+"""empty message
+
+Revision ID: 7ff57806ffd5
+Revises: 2f3c3597c78d
+Create Date: 2019-01-29 02:57:50.279918
+
+"""
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.dialects import postgresql
+
+# revision identifiers, used by Alembic.
+revision = '7ff57806ffd5'
+down_revision = '2f3c3597c78d'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.execute("""
+ DROP TYPE IF EXISTS tsq_state CASCADE;
+
+CREATE TYPE tsq_state AS (
+ search_query text,
+ parentheses_stack int,
+ skip_for int,
+ current_token text,
+ current_index int,
+ current_char text,
+ previous_char text,
+ tokens text[]
+);
+
+CREATE OR REPLACE FUNCTION tsq_append_current_token(state tsq_state)
+RETURNS tsq_state AS $$
+BEGIN
+ IF state.current_token != '' THEN
+ state.tokens := array_append(state.tokens, state.current_token);
+ state.current_token := '';
+ END IF;
+ RETURN state;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE;
+
+
+CREATE OR REPLACE FUNCTION tsq_tokenize_character(state tsq_state)
+RETURNS tsq_state AS $$
+BEGIN
+ IF state.current_char = '(' THEN
+ state.tokens := array_append(state.tokens, '(');
+ state.parentheses_stack := state.parentheses_stack + 1;
+ state := tsq_append_current_token(state);
+ ELSIF state.current_char = ')' THEN
+ IF (state.parentheses_stack > 0 AND state.current_token != '') THEN
+ state := tsq_append_current_token(state);
+ state.tokens := array_append(state.tokens, ')');
+ state.parentheses_stack := state.parentheses_stack - 1;
+ END IF;
+ ELSIF state.current_char = '"' THEN
+ state.skip_for := position('"' IN substring(
+ state.search_query FROM state.current_index + 1
+ ));
+
+ IF state.skip_for > 1 THEN
+ state.tokens = array_append(
+ state.tokens,
+ substring(
+ state.search_query
+ FROM state.current_index FOR state.skip_for + 1
+ )
+ );
+ ELSIF state.skip_for = 0 THEN
+ state.current_token := state.current_token || state.current_char;
+ END IF;
+ ELSIF (
+ state.current_char = '-' AND
+ (state.current_index = 1 OR state.previous_char = ' ')
+ ) THEN
+ state.tokens := array_append(state.tokens, '-');
+ ELSIF state.current_char = ' ' THEN
+ state := tsq_append_current_token(state);
+ IF substring(
+ state.search_query FROM state.current_index FOR 4
+ ) = ' or ' THEN
+ state.skip_for := 2;
+
+ -- remove duplicate OR tokens
+ IF state.tokens[array_length(state.tokens, 1)] != ' | ' THEN
+ state.tokens := array_append(state.tokens, ' | ');
+ END IF;
+ END IF;
+ ELSE
+ state.current_token = state.current_token || state.current_char;
+ END IF;
+ RETURN state;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE;
+
+
+CREATE OR REPLACE FUNCTION tsq_tokenize(search_query text) RETURNS text[] AS $$
+DECLARE
+ state tsq_state;
+BEGIN
+ SELECT
+ search_query::text AS search_query,
+ 0::int AS parentheses_stack,
+ 0 AS skip_for,
+ ''::text AS current_token,
+ 0 AS current_index,
+ ''::text AS current_char,
+ ''::text AS previous_char,
+ '{}'::text[] AS tokens
+ INTO state;
+
+ state.search_query := lower(trim(
+ regexp_replace(search_query, '""+', '""', 'g')
+ ));
+
+ FOR state.current_index IN (
+ SELECT generate_series(1, length(state.search_query))
+ ) LOOP
+ state.current_char := substring(
+ search_query FROM state.current_index FOR 1
+ );
+
+ IF state.skip_for > 0 THEN
+ state.skip_for := state.skip_for - 1;
+ CONTINUE;
+ END IF;
+
+ state := tsq_tokenize_character(state);
+ state.previous_char := state.current_char;
+ END LOOP;
+ state := tsq_append_current_token(state);
+
+ state.tokens := array_nremove(state.tokens, '(', -state.parentheses_stack);
+
+ RETURN state.tokens;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE;
+
+
+-- Processes an array of text search tokens and returns a tsquery
+CREATE OR REPLACE FUNCTION tsq_process_tokens(config regconfig, tokens text[])
+RETURNS tsquery AS $$
+DECLARE
+ result_query text;
+ previous_value text;
+ value text;
+BEGIN
+ result_query := '';
+ FOREACH value IN ARRAY tokens LOOP
+ IF value = '"' THEN
+ CONTINUE;
+ END IF;
+
+ IF left(value, 1) = '"' AND right(value, 1) = '"' THEN
+ value := phraseto_tsquery(config, value);
+ ELSIF value NOT IN ('(', ' | ', ')', '-') THEN
+ value := quote_literal(value) || ':*';
+ END IF;
+
+ IF previous_value = '-' THEN
+ IF value = '(' THEN
+ value := '!' || value;
+ ELSE
+ value := '!(' || value || ')';
+ END IF;
+ END IF;
+
+ SELECT
+ CASE
+ WHEN result_query = '' THEN value
+ WHEN (
+ previous_value IN ('!(', '(', ' | ') OR
+ value IN (')', ' | ')
+ ) THEN result_query || value
+ ELSE result_query || ' & ' || value
+ END
+ INTO result_query;
+ previous_value := value;
+ END LOOP;
+
+ RETURN to_tsquery(config, result_query);
+END;
+$$ LANGUAGE plpgsql IMMUTABLE;
+
+
+CREATE OR REPLACE FUNCTION tsq_process_tokens(tokens text[])
+RETURNS tsquery AS $$
+ SELECT tsq_process_tokens(get_current_ts_config(), tokens);
+$$ LANGUAGE SQL IMMUTABLE;
+
+
+CREATE OR REPLACE FUNCTION tsq_parse(config regconfig, search_query text)
+RETURNS tsquery AS $$
+ SELECT tsq_process_tokens(config, tsq_tokenize(search_query));
+$$ LANGUAGE SQL IMMUTABLE;
+
+
+CREATE OR REPLACE FUNCTION tsq_parse(config text, search_query text)
+RETURNS tsquery AS $$
+ SELECT tsq_parse(config::regconfig, search_query);
+$$ LANGUAGE SQL IMMUTABLE;
+
+
+CREATE OR REPLACE FUNCTION tsq_parse(search_query text) RETURNS tsquery AS $$
+ SELECT tsq_parse(get_current_ts_config(), search_query);
+$$ LANGUAGE SQL IMMUTABLE;
+
+
+-- remove first N elements equal to the given value from the array (array
+-- must be one-dimensional)
+--
+-- If negative value is given as the third argument the removal of elements
+-- starts from the last array element.
+CREATE OR REPLACE FUNCTION array_nremove(anyarray, anyelement, int)
+RETURNS ANYARRAY AS $$
+ WITH replaced_positions AS (
+ SELECT UNNEST(
+ CASE
+ WHEN $2 IS NULL THEN
+ '{}'::int[]
+ WHEN $3 > 0 THEN
+ (array_positions($1, $2))[1:$3]
+ WHEN $3 < 0 THEN
+ (array_positions($1, $2))[
+ (cardinality(array_positions($1, $2)) + $3 + 1):
+ ]
+ ELSE
+ '{}'::int[]
+ END
+ ) AS position
+ )
+ SELECT COALESCE((
+ SELECT array_agg(value)
+ FROM unnest($1) WITH ORDINALITY AS t(value, index)
+ WHERE index NOT IN (SELECT position FROM replaced_positions)
+ ), $1[1:0]);
+$$ LANGUAGE SQL IMMUTABLE;
+""")
+ # ### end Alembic commands ###
+
+
+def downgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ pass
+ # ### end Alembic commands ###