From 100a0c57b775b85056ef5a2367da43e567b5a3ab Mon Sep 17 00:00:00 2001 From: Lukas Fittl Date: Mon, 1 Jul 2019 00:59:43 -0700 Subject: [PATCH] Escape identifiers in more cases, if necessary Based on work done in https://github.com/lfittl/pg_query/pull/136 --- lib/pg_query/deparse.rb | 21 +- lib/pg_query/deparse/keywords.rb | 760 ++++++++++++++++++++++++++++++ spec/lib/pg_query/deparse_spec.rb | 22 +- 3 files changed, 787 insertions(+), 16 deletions(-) create mode 100644 lib/pg_query/deparse/keywords.rb diff --git a/lib/pg_query/deparse.rb b/lib/pg_query/deparse.rb index 467f0f16..0e764b73 100644 --- a/lib/pg_query/deparse.rb +++ b/lib/pg_query/deparse.rb @@ -1,5 +1,7 @@ -require_relative 'deparse/interval' require_relative 'deparse/alter_table' +require_relative 'deparse/interval' +require_relative 'deparse/keywords' + class PgQuery # Reconstruct all of the parsed queries into their original form def deparse(tree = @tree) @@ -194,7 +196,7 @@ def deparse_item(item, context = nil) # rubocop:disable Metrics/CyclomaticComple elsif [FUNC_CALL, TYPE_NAME, :operator, :defname_as].include?(context) node['str'] else - format('"%s"', node['str'].gsub('"', '""')) + deparse_identifier(node['str'], escape_always: true) end when INTEGER node['ival'].to_s @@ -211,6 +213,15 @@ def deparse_item_list(nodes, context = nil) nodes.map { |n| deparse_item(n, context) } end + def deparse_identifier(ident, escape_always: false) + return if ident.nil? + if escape_always || !ident[/^\w+$/] || KEYWORDS.include?(ident.upcase) + format('"%s"', ident.gsub('"', '""')) + else + ident + end + end + def deparse_rangevar(node) output = [] output << 'ONLY' unless node['inh'] @@ -283,7 +294,7 @@ def deparse_alias(node) if node['colnames'] name + '(' + deparse_item_list(node['colnames']).join(', ') + ')' else - name + deparse_identifier(name) end end @@ -334,7 +345,7 @@ def deparse_paramref(node) def deparse_restarget(node, context) if context == :select - [deparse_item(node['val']), node['name']].compact.join(' AS ') + [deparse_item(node['val']), deparse_identifier(node['name'])].compact.join(' AS ') elsif context == :update [node['name'], deparse_item(node['val'])].compact.join(' = ') elsif node['val'].nil? @@ -446,7 +457,7 @@ def deparse_role_spec(node) return 'CURRENT_USER' if node['roletype'] == 1 return 'SESSION_USER' if node['roletype'] == 2 return 'PUBLIC' if node['roletype'] == 3 - format('"%s"', node['rolename'].gsub('"', '""')) + deparse_identifier(node['rolename'], escape_always: true) end def deparse_aexpr_in(node) diff --git a/lib/pg_query/deparse/keywords.rb b/lib/pg_query/deparse/keywords.rb new file mode 100644 index 00000000..e5ba84e6 --- /dev/null +++ b/lib/pg_query/deparse/keywords.rb @@ -0,0 +1,760 @@ +class PgQuery + module Deparse # rubocop:disable Metrics/ModuleLength + # Copy of https://www.postgresql.org/docs/current/sql-keywords-appendix.html + # to make sure we escape identifiers that are reserved or non-reserved keywords + KEYWORDS = [ + 'A', + 'ABORT', + 'ABS', + 'ABSENT', + 'ABSOLUTE', + 'ACCESS', + 'ACCORDING', + 'ACTION', + 'ADA', + 'ADD', + 'ADMIN', + 'AFTER', + 'AGGREGATE', + 'ALL', + 'ALLOCATE', + 'ALSO', + 'ALTER', + 'ALWAYS', + 'ANALYSE', + 'ANALYZE', + 'AND', + 'ANY', + 'ARE', + 'ARRAY', + 'ARRAY_AGG', + 'ARRAY_MAX_CARDINALITY', + 'AS', + 'ASC', + 'ASENSITIVE', + 'ASSERTION', + 'ASSIGNMENT', + 'ASYMMETRIC', + 'AT', + 'ATOMIC', + 'ATTRIBUTE', + 'ATTRIBUTES', + 'AUTHORIZATION', + 'AVG', + 'BACKWARD', + 'BASE64', + 'BEFORE', + 'BEGIN', + 'BEGIN_FRAME', + 'BEGIN_PARTITION', + 'BERNOULLI', + 'BETWEEN', + 'BIGINT', + 'BINARY', + 'BIT', + 'BIT_LENGTH', + 'BLOB', + 'BLOCKED', + 'BOM', + 'BOOLEAN', + 'BOTH', + 'BREADTH', + 'BY', + 'C', + 'CACHE', + 'CALL', + 'CALLED', + 'CARDINALITY', + 'CASCADE', + 'CASCADED', + 'CASE', + 'CAST', + 'CATALOG', + 'CATALOG_NAME', + 'CEIL', + 'CEILING', + 'CHAIN', + 'CHAR', + 'CHARACTER', + 'CHARACTERISTICS', + 'CHARACTERS', + 'CHARACTER_LENGTH', + 'CHARACTER_SET_CATALOG', + 'CHARACTER_SET_NAME', + 'CHARACTER_SET_SCHEMA', + 'CHAR_LENGTH', + 'CHECK', + 'CHECKPOINT', + 'CLASS', + 'CLASS_ORIGIN', + 'CLOB', + 'CLOSE', + 'CLUSTER', + 'COALESCE', + 'COBOL', + 'COLLATE', + 'COLLATION', + 'COLLATION_CATALOG', + 'COLLATION_NAME', + 'COLLATION_SCHEMA', + 'COLLECT', + 'COLUMN', + 'COLUMNS', + 'COLUMN_NAME', + 'COMMAND_FUNCTION', + 'COMMAND_FUNCTION_CODE', + 'COMMENT', + 'COMMENTS', + 'COMMIT', + 'COMMITTED', + 'CONCURRENTLY', + 'CONDITION', + 'CONDITION_NUMBER', + 'CONFIGURATION', + 'CONFLICT', + 'CONNECT', + 'CONNECTION', + 'CONNECTION_NAME', + 'CONSTRAINT', + 'CONSTRAINTS', + 'CONSTRAINT_CATALOG', + 'CONSTRAINT_NAME', + 'CONSTRAINT_SCHEMA', + 'CONSTRUCTOR', + 'CONTAINS', + 'CONTENT', + 'CONTINUE', + 'CONTROL', + 'CONVERSION', + 'CONVERT', + 'COPY', + 'CORR', + 'CORRESPONDING', + 'COST', + 'COUNT', + 'COVAR_POP', + 'COVAR_SAMP', + 'CREATE', + 'CROSS', + 'CSV', + 'CUBE', + 'CUME_DIST', + 'CURRENT', + 'CURRENT_CATALOG', + 'CURRENT_DATE', + 'CURRENT_DEFAULT_TRANSFORM_GROUP', + 'CURRENT_PATH', + 'CURRENT_ROLE', + 'CURRENT_ROW', + 'CURRENT_SCHEMA', + 'CURRENT_TIME', + 'CURRENT_TIMESTAMP', + 'CURRENT_TRANSFORM_GROUP_FOR_TYPE', + 'CURRENT_USER', + 'CURSOR', + 'CURSOR_NAME', + 'CYCLE', + 'DATA', + 'DATABASE', + 'DATALINK', + 'DATE', + 'DATETIME_INTERVAL_CODE', + 'DATETIME_INTERVAL_PRECISION', + 'DAY', + 'DB', + 'DEALLOCATE', + 'DEC', + 'DECIMAL', + 'DECLARE', + 'DEFAULT', + 'DEFAULTS', + 'DEFERRABLE', + 'DEFERRED', + 'DEFINED', + 'DEFINER', + 'DEGREE', + 'DELETE', + 'DELIMITER', + 'DELIMITERS', + 'DENSE_RANK', + 'DEPENDS', + 'DEPTH', + 'DEREF', + 'DERIVED', + 'DESC', + 'DESCRIBE', + 'DESCRIPTOR', + 'DETERMINISTIC', + 'DIAGNOSTICS', + 'DICTIONARY', + 'DISABLE', + 'DISCARD', + 'DISCONNECT', + 'DISPATCH', + 'DISTINCT', + 'DLNEWCOPY', + 'DLPREVIOUSCOPY', + 'DLURLCOMPLETE', + 'DLURLCOMPLETEONLY', + 'DLURLCOMPLETEWRITE', + 'DLURLPATH', + 'DLURLPATHONLY', + 'DLURLPATHWRITE', + 'DLURLSCHEME', + 'DLURLSERVER', + 'DLVALUE', + 'DO', + 'DOCUMENT', + 'DOMAIN', + 'DOUBLE', + 'DROP', + 'DYNAMIC', + 'DYNAMIC_FUNCTION', + 'DYNAMIC_FUNCTION_CODE', + 'EACH', + 'ELEMENT', + 'ELSE', + 'EMPTY', + 'ENABLE', + 'ENCODING', + 'ENCRYPTED', + 'END', + 'END-EXEC', + 'END_FRAME', + 'END_PARTITION', + 'ENFORCED', + 'ENUM', + 'EQUALS', + 'ESCAPE', + 'EVENT', + 'EVERY', + 'EXCEPT', + 'EXCEPTION', + 'EXCLUDE', + 'EXCLUDING', + 'EXCLUSIVE', + 'EXEC', + 'EXECUTE', + 'EXISTS', + 'EXP', + 'EXPLAIN', + 'EXPRESSION', + 'EXTENSION', + 'EXTERNAL', + 'EXTRACT', + 'FALSE', + 'FAMILY', + 'FETCH', + 'FILE', + 'FILTER', + 'FINAL', + 'FIRST', + 'FIRST_VALUE', + 'FLAG', + 'FLOAT', + 'FLOOR', + 'FOLLOWING', + 'FOR', + 'FORCE', + 'FOREIGN', + 'FORTRAN', + 'FORWARD', + 'FOUND', + 'FRAME_ROW', + 'FREE', + 'FREEZE', + 'FROM', + 'FS', + 'FULL', + 'FUNCTION', + 'FUNCTIONS', + 'FUSION', + 'G', + 'GENERAL', + 'GENERATED', + 'GET', + 'GLOBAL', + 'GO', + 'GOTO', + 'GRANT', + 'GRANTED', + 'GREATEST', + 'GROUP', + 'GROUPING', + 'GROUPS', + 'HANDLER', + 'HAVING', + 'HEADER', + 'HEX', + 'HIERARCHY', + 'HOLD', + 'HOUR', + 'ID', + 'IDENTITY', + 'IF', + 'IGNORE', + 'ILIKE', + 'IMMEDIATE', + 'IMMEDIATELY', + 'IMMUTABLE', + 'IMPLEMENTATION', + 'IMPLICIT', + 'IMPORT', + 'IN', + 'INCLUDING', + 'INCREMENT', + 'INDENT', + 'INDEX', + 'INDEXES', + 'INDICATOR', + 'INHERIT', + 'INHERITS', + 'INITIALLY', + 'INLINE', + 'INNER', + 'INOUT', + 'INPUT', + 'INSENSITIVE', + 'INSERT', + 'INSTANCE', + 'INSTANTIABLE', + 'INSTEAD', + 'INT', + 'INTEGER', + 'INTEGRITY', + 'INTERSECT', + 'INTERSECTION', + 'INTERVAL', + 'INTO', + 'INVOKER', + 'IS', + 'ISNULL', + 'ISOLATION', + 'JOIN', + 'K', + 'KEY', + 'KEY_MEMBER', + 'KEY_TYPE', + 'LABEL', + 'LAG', + 'LANGUAGE', + 'LARGE', + 'LAST', + 'LAST_VALUE', + 'LATERAL', + 'LEAD', + 'LEADING', + 'LEAKPROOF', + 'LEAST', + 'LEFT', + 'LENGTH', + 'LEVEL', + 'LIBRARY', + 'LIKE', + 'LIKE_REGEX', + 'LIMIT', + 'LINK', + 'LISTEN', + 'LN', + 'LOAD', + 'LOCAL', + 'LOCALTIME', + 'LOCALTIMESTAMP', + 'LOCATION', + 'LOCATOR', + 'LOCK', + 'LOCKED', + 'LOGGED', + 'LOWER', + 'M', + 'MAP', + 'MAPPING', + 'MATCH', + 'MATCHED', + 'MATERIALIZED', + 'MAX', + 'MAXVALUE', + 'MAX_CARDINALITY', + 'MEMBER', + 'MERGE', + 'MESSAGE_LENGTH', + 'MESSAGE_OCTET_LENGTH', + 'MESSAGE_TEXT', + 'METHOD', + 'MIN', + 'MINUTE', + 'MINVALUE', + 'MOD', + 'MODE', + 'MODIFIES', + 'MODULE', + 'MONTH', + 'MORE', + 'MOVE', + 'MULTISET', + 'MUMPS', + 'NAME', + 'NAMES', + 'NAMESPACE', + 'NATIONAL', + 'NATURAL', + 'NCHAR', + 'NCLOB', + 'NESTING', + 'NEW', + 'NEXT', + 'NFC', + 'NFD', + 'NFKC', + 'NFKD', + 'NIL', + 'NO', + 'NONE', + 'NORMALIZE', + 'NORMALIZED', + 'NOT', + 'NOTHING', + 'NOTIFY', + 'NOTNULL', + 'NOWAIT', + 'NTH_VALUE', + 'NTILE', + 'NULL', + 'NULLABLE', + 'NULLIF', + 'NULLS', + 'NUMBER', + 'NUMERIC', + 'OBJECT', + 'OCCURRENCES_REGEX', + 'OCTETS', + 'OCTET_LENGTH', + 'OF', + 'OFF', + 'OFFSET', + 'OIDS', + 'OLD', + 'ON', + 'ONLY', + 'OPEN', + 'OPERATOR', + 'OPTION', + 'OPTIONS', + 'OR', + 'ORDER', + 'ORDERING', + 'ORDINALITY', + 'OTHERS', + 'OUT', + 'OUTER', + 'OUTPUT', + 'OVER', + 'OVERLAPS', + 'OVERLAY', + 'OVERRIDING', + 'OWNED', + 'OWNER', + 'P', + 'PAD', + 'PARALLEL', + 'PARAMETER', + 'PARAMETER_MODE', + 'PARAMETER_NAME', + 'PARAMETER_ORDINAL_POSITION', + 'PARAMETER_SPECIFIC_CATALOG', + 'PARAMETER_SPECIFIC_NAME', + 'PARAMETER_SPECIFIC_SCHEMA', + 'PARSER', + 'PARTIAL', + 'PARTITION', + 'PASCAL', + 'PASSING', + 'PASSTHROUGH', + 'PASSWORD', + 'PATH', + 'PERCENT', + 'PERCENTILE_CONT', + 'PERCENTILE_DISC', + 'PERCENT_RANK', + 'PERIOD', + 'PERMISSION', + 'PLACING', + 'PLANS', + 'PLI', + 'POLICY', + 'PORTION', + 'POSITION', + 'POSITION_REGEX', + 'POWER', + 'PRECEDES', + 'PRECEDING', + 'PRECISION', + 'PREPARE', + 'PREPARED', + 'PRESERVE', + 'PRIMARY', + 'PRIOR', + 'PRIVILEGES', + 'PROCEDURAL', + 'PROCEDURE', + 'PROGRAM', + 'PUBLIC', + 'QUOTE', + 'RANGE', + 'RANK', + 'READ', + 'READS', + 'REAL', + 'REASSIGN', + 'RECHECK', + 'RECOVERY', + 'RECURSIVE', + 'REF', + 'REFERENCES', + 'REFERENCING', + 'REFRESH', + 'REGR_AVGX', + 'REGR_AVGY', + 'REGR_COUNT', + 'REGR_INTERCEPT', + 'REGR_R2', + 'REGR_SLOPE', + 'REGR_SXX', + 'REGR_SXY', + 'REGR_SYY', + 'REINDEX', + 'RELATIVE', + 'RELEASE', + 'RENAME', + 'REPEATABLE', + 'REPLACE', + 'REPLICA', + 'REQUIRING', + 'RESET', + 'RESPECT', + 'RESTART', + 'RESTORE', + 'RESTRICT', + 'RESULT', + 'RETURN', + 'RETURNED_CARDINALITY', + 'RETURNED_LENGTH', + 'RETURNED_OCTET_LENGTH', + 'RETURNED_SQLSTATE', + 'RETURNING', + 'RETURNS', + 'REVOKE', + 'RIGHT', + 'ROLE', + 'ROLLBACK', + 'ROLLUP', + 'ROUTINE', + 'ROUTINE_CATALOG', + 'ROUTINE_NAME', + 'ROUTINE_SCHEMA', + 'ROW', + 'ROWS', + 'ROW_COUNT', + 'ROW_NUMBER', + 'RULE', + 'SAVEPOINT', + 'SCALE', + 'SCHEMA', + 'SCHEMA_NAME', + 'SCOPE', + 'SCOPE_CATALOG', + 'SCOPE_NAME', + 'SCOPE_SCHEMA', + 'SCROLL', + 'SEARCH', + 'SECOND', + 'SECTION', + 'SECURITY', + 'SELECT', + 'SELECTIVE', + 'SELF', + 'SENSITIVE', + 'SEQUENCE', + 'SEQUENCES', + 'SERIALIZABLE', + 'SERVER', + 'SERVER_NAME', + 'SESSION', + 'SESSION_USER', + 'SET', + 'SETOF', + 'SETS', + 'SHARE', + 'SHOW', + 'SIMILAR', + 'SIMPLE', + 'SIZE', + 'SKIP', + 'SMALLINT', + 'SNAPSHOT', + 'SOME', + 'SOURCE', + 'SPACE', + 'SPECIFIC', + 'SPECIFICTYPE', + 'SPECIFIC_NAME', + 'SQL', + 'SQLCODE', + 'SQLERROR', + 'SQLEXCEPTION', + 'SQLSTATE', + 'SQLWARNING', + 'SQRT', + 'STABLE', + 'STANDALONE', + 'START', + 'STATE', + 'STATEMENT', + 'STATIC', + 'STATISTICS', + 'STDDEV_POP', + 'STDDEV_SAMP', + 'STDIN', + 'STDOUT', + 'STORAGE', + 'STRICT', + 'STRIP', + 'STRUCTURE', + 'STYLE', + 'SUBCLASS_ORIGIN', + 'SUBMULTISET', + 'SUBSTRING', + 'SUBSTRING_REGEX', + 'SUCCEEDS', + 'SUM', + 'SYMMETRIC', + 'SYSID', + 'SYSTEM', + 'SYSTEM_TIME', + 'SYSTEM_USER', + 'T', + 'TABLE', + 'TABLES', + 'TABLESAMPLE', + 'TABLESPACE', + 'TABLE_NAME', + 'TEMP', + 'TEMPLATE', + 'TEMPORARY', + 'TEXT', + 'THEN', + 'TIES', + 'TIME', + 'TIMESTAMP', + 'TIMEZONE_HOUR', + 'TIMEZONE_MINUTE', + 'TO', + 'TOKEN', + 'TOP_LEVEL_COUNT', + 'TRAILING', + 'TRANSACTION', + 'TRANSACTIONS_COMMITTED', + 'TRANSACTIONS_ROLLED_BACK', + 'TRANSACTION_ACTIVE', + 'TRANSFORM', + 'TRANSFORMS', + 'TRANSLATE', + 'TRANSLATE_REGEX', + 'TRANSLATION', + 'TREAT', + 'TRIGGER', + 'TRIGGER_CATALOG', + 'TRIGGER_NAME', + 'TRIGGER_SCHEMA', + 'TRIM', + 'TRIM_ARRAY', + 'TRUE', + 'TRUNCATE', + 'TRUSTED', + 'TYPE', + 'TYPES', + 'UESCAPE', + 'UNBOUNDED', + 'UNCOMMITTED', + 'UNDER', + 'UNENCRYPTED', + 'UNION', + 'UNIQUE', + 'UNKNOWN', + 'UNLINK', + 'UNLISTEN', + 'UNLOGGED', + 'UNNAMED', + 'UNNEST', + 'UNTIL', + 'UNTYPED', + 'UPDATE', + 'UPPER', + 'URI', + 'USAGE', + 'USER', + 'USER_DEFINED_TYPE_CATALOG', + 'USER_DEFINED_TYPE_CODE', + 'USER_DEFINED_TYPE_NAME', + 'USER_DEFINED_TYPE_SCHEMA', + 'USING', + 'VACUUM', + 'VALID', + 'VALIDATE', + 'VALIDATOR', + 'VALUE', + 'VALUES', + 'VALUE_OF', + 'VARBINARY', + 'VARCHAR', + 'VARIADIC', + 'VARYING', + 'VAR_POP', + 'VAR_SAMP', + 'VERBOSE', + 'VERSION', + 'VERSIONING', + 'VIEW', + 'VIEWS', + 'VOLATILE', + 'WHEN', + 'WHENEVER', + 'WHERE', + 'WHITESPACE', + 'WIDTH_BUCKET', + 'WINDOW', + 'WITH', + 'WITHIN', + 'WITHOUT', + 'WORK', + 'WRAPPER', + 'WRITE', + 'XML', + 'XMLAGG', + 'XMLATTRIBUTES', + 'XMLBINARY', + 'XMLCAST', + 'XMLCOMMENT', + 'XMLCONCAT', + 'XMLDECLARATION', + 'XMLDOCUMENT', + 'XMLELEMENT', + 'XMLEXISTS', + 'XMLFOREST', + 'XMLITERATE', + 'XMLNAMESPACES', + 'XMLPARSE', + 'XMLPI', + 'XMLQUERY', + 'XMLROOT', + 'XMLSCHEMA', + 'XMLSERIALIZE', + 'XMLTABLE', + 'XMLTEXT', + 'XMLVALIDATE', + 'YEAR', + 'YES', + 'ZONE' + ].freeze + end +end diff --git a/spec/lib/pg_query/deparse_spec.rb b/spec/lib/pg_query/deparse_spec.rb index 48b763cc..9019078f 100644 --- a/spec/lib/pg_query/deparse_spec.rb +++ b/spec/lib/pg_query/deparse_spec.rb @@ -118,12 +118,12 @@ SELECT "g"."id", "g"."link", "g"."data", 1, ARRAY[ROW("g"."f1", "g"."f2")], false - FROM "graph" g + FROM "graph" "g" UNION ALL SELECT "g"."id", "g"."link", "g"."data", "sg"."depth" + 1, "path" || ROW("g"."f1", "g"."f2"), ROW("g"."f1", "g"."f2") = ANY("path") - FROM "graph" g, "search_graph" sg + FROM "graph" "g", "search_graph" sg WHERE "g"."id" = "sg"."link" AND NOT "cycle" ) SELECT "id", "data", "link" FROM "search_graph"; @@ -140,7 +140,7 @@ end context 'LATERAL' do - let(:query) { 'SELECT "m"."name" AS mname, "pname" FROM "manufacturers" m, LATERAL get_product_names("m"."id") pname' } + let(:query) { 'SELECT "m"."name" AS mname, "pname" FROM "manufacturers" "m", LATERAL get_product_names("m"."id") pname' } it { is_expected.to eq query } end @@ -149,7 +149,7 @@ let(:query) do %( SELECT "m"."name" AS mname, "pname" - FROM "manufacturers" m LEFT JOIN LATERAL get_product_names("m"."id") pname ON true + FROM "manufacturers" "m" LEFT JOIN LATERAL get_product_names("m"."id") pname ON true ) end @@ -227,7 +227,7 @@ end context 'basic CASE WHEN statements' do - let(:query) { 'SELECT CASE WHEN "a"."status" = 1 THEN \'active\' WHEN "a"."status" = 2 THEN \'inactive\' END FROM "accounts" a' } + let(:query) { 'SELECT CASE WHEN "a"."status" = 1 THEN \'active\' WHEN "a"."status" = 2 THEN \'inactive\' END FROM "accounts" "a"' } it { is_expected.to eq query } end @@ -239,7 +239,7 @@ end context 'CASE WHEN statements with ELSE clause' do - let(:query) { 'SELECT CASE WHEN "a"."status" = 1 THEN \'active\' WHEN "a"."status" = 2 THEN \'inactive\' ELSE \'unknown\' END FROM "accounts" a' } + let(:query) { 'SELECT CASE WHEN "a"."status" = 1 THEN \'active\' WHEN "a"."status" = 2 THEN \'inactive\' ELSE \'unknown\' END FROM "accounts" "a"' } it { is_expected.to eq query } end @@ -263,7 +263,7 @@ end context 'Subselect in FROM clause' do - let(:query) { "SELECT * FROM (SELECT generate_series(0, 100)) a" } + let(:query) { "SELECT * FROM (SELECT generate_series(0, 100)) \"a\"" } it { is_expected.to eq query } end @@ -401,7 +401,7 @@ end context 'NULLIF' do - let(:query) { 'SELECT NULLIF("id", 0) AS id FROM "x"' } + let(:query) { 'SELECT NULLIF("id", 0) AS "id" FROM "x"' } it { is_expected.to eq query } end @@ -1503,7 +1503,7 @@ let(:query) do ''' SELECT "m"."name" AS mname, "pname" - FROM "manufacturers" m LEFT JOIN LATERAL get_product_names("m"."id") pname ON true + FROM "manufacturers" "m" LEFT JOIN LATERAL get_product_names("m"."id") pname ON true ''' end @@ -1514,7 +1514,7 @@ let(:query) do ''' SELECT "m"."name" AS mname, "pname" - FROM "manufacturers" m LEFT JOIN LATERAL get_product_names("m"."id") pname ON true; + FROM "manufacturers" "m" LEFT JOIN LATERAL get_product_names("m"."id") pname ON true; INSERT INTO "manufacturers_daily" (a, b) SELECT "a", "b" FROM "manufacturers"; ''' @@ -1527,7 +1527,7 @@ let(:query) do ''' SELECT "m"."name" AS mname, "pname" - FROM "manufacturers" m LEFT JOIN LATERAL get_product_names("m"."id") pname ON true; + FROM "manufacturers" "m" LEFT JOIN LATERAL get_product_names("m"."id") pname ON true; UPDATE "users" SET name = \'bobby; drop tables\'; INSERT INTO "manufacturers_daily" (a, b) SELECT "a", "b" FROM "manufacturers";