Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Alter .. RENAME SQL #146

Merged
merged 5 commits into from
Nov 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 43 additions & 11 deletions lib/pg_query/deparse.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require_relative 'deparse/alter_table'
require_relative 'deparse/rename'
require_relative 'deparse/interval'
require_relative 'deparse/keywords'

Expand Down Expand Up @@ -102,6 +103,8 @@ def deparse_item(item, context = nil) # rubocop:disable Metrics/CyclomaticComple
deparse_copy(node)
when CREATE_CAST_STMT
deparse_create_cast(node)
when CREATE_DOMAIN_STMT
deparse_create_domain(node)
when CREATE_ENUM_STMT
deparse_create_enum(node)
when CREATE_FUNCTION_STMT
Expand Down Expand Up @@ -241,19 +244,32 @@ def deparse_raw_stmt(node)
deparse_item(node[STMT_FIELD])
end

def deparse_renamestmt(node)
output = []

case node['renameType']
when OBJECT_TYPE_TABLE
output << 'ALTER TABLE'
output << deparse_item(node['relation'])
output << 'RENAME TO'
output << node['newname']
def deparse_renamestmt_decision(node, type)
if node[type]
if node[type].is_a?(String)
deparse_identifier(node[type])
elsif node[type].is_a?(Array)
deparse_item_list(node[type])
elsif node[type].is_a?(Object)
deparse_item(node[type])
end
else
raise format("Can't deparse: %s", node.inspect)
type
end
end

def deparse_renamestmt(node)
output = []
output << 'ALTER'
command, type, options, type2, options2 = Rename.commands(node)

output << command if command
output << deparse_renamestmt_decision(node, type)
output << options if options
output << deparse_renamestmt_decision(node, type2) if type2
output << deparse_renamestmt_decision(node, options2) if options2
output << 'TO'
output << deparse_identifier(node['newname'], escape_always: true)
output.join(' ')
end

Expand Down Expand Up @@ -306,7 +322,10 @@ def deparse_alias(node)

def deparse_alter_table(node)
output = []
output << 'ALTER TABLE'
output << 'ALTER'

output << 'TABLE' if node['relkind'] == OBJECT_TYPE_TABLE
output << 'VIEW' if node['relkind'] == OBJECT_TYPE_VIEW

output << deparse_item(node['relation'])

Expand Down Expand Up @@ -801,6 +820,7 @@ def deparse_constraint(node) # rubocop:disable Metrics/CyclomaticComplexity
if node['raw_expr']
expression = deparse_item(node['raw_expr'])
# Unless it's simple, put parentheses around it
expression = '(' + expression + ')' if node['raw_expr'][BOOL_EXPR]
expression = '(' + expression + ')' if node['raw_expr'][A_EXPR] && node['raw_expr'][A_EXPR]['kind'] == AEXPR_OP
output << expression
end
Expand Down Expand Up @@ -862,6 +882,18 @@ def deparse_create_cast(node)
output.join(' ')
end

def deparse_create_domain(node)
output = []
output << 'CREATE'
output << 'DOMAIN'
output << deparse_item_list(node['domainname']).join('.')
output << 'AS'
output << deparse_item(node['typeName']) if node['typeName']
output << deparse_item(node['collClause']) if node['collClause']
output << deparse_item_list(node['constraints'])
output.join(' ')
end

def deparse_create_function(node)
output = []
output << 'CREATE'
Expand Down
41 changes: 41 additions & 0 deletions lib/pg_query/deparse/rename.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
class PgQuery
module Deparse
module Rename
# relation, subname and object is the array key in the node.
# Array return five value. First is the type like a TRIGGER, TABLE, DOMAIN
# Other values may be parameter or SQL key.
#
# If node['renameType'] is the integer 13 (OBJECT_TYPE_DOMCONSTRAINT),
# then return value of this method will be:
#
# %w[DOMAIN object RENAME CONSTRAINT subname]
#
# Which will be composed into the SQL as:
#
# ALTER {type} {name} RENAME CONSTRAINT {subname} TO {newname}
#
def self.commands(node)
action = RENAME_MAPPING[node['renameType']] || raise(format("Can't deparse: %s", node.inspect))
PgQuery::Deparse.instance_exec(node, &action)
end

RENAME_MAPPING = {
OBJECT_TYPE_CONVERSION => ->(_node) { %w[CONVERSION object RENAME] },
OBJECT_TYPE_TABLE => ->(_node) { %w[TABLE relation RENAME] },
OBJECT_TYPE_TABCONSTRAINT => ->(_node) { %w[TABLE relation RENAME CONSTRAINT subname] },
OBJECT_TYPE_INDEX => ->(_node) { %w[INDEX relation RENAME] },
OBJECT_TYPE_MATVIEW => ->(_node) { ['MATERIALIZED VIEW', 'relation', 'RENAME'] },
OBJECT_TYPE_TABLESPACE => ->(_node) { %w[TABLESPACE subname RENAME] },
OBJECT_TYPE_VIEW => ->(_node) { %w[VIEW relation RENAME] },
OBJECT_TYPE_COLUMN => ->(_node) { %w[TABLE relation RENAME COLUMN subname] },
OBJECT_TYPE_COLLATION => ->(_node) { %w[COLLATION object RENAME] },
OBJECT_TYPE_TYPE => ->(_node) { %w[TYPE object RENAME] },
OBJECT_TYPE_DOMCONSTRAINT => ->(_node) { %w[DOMAIN object RENAME CONSTRAINT subname] },
OBJECT_TYPE_RULE => ->(_node) { %w[RULE subname ON relation RENAME] },
OBJECT_TYPE_TRIGGER => ->(_node) { %w[TRIGGER subname ON relation RENAME] },
OBJECT_TYPE_AGGREGATE => ->(_node) { %w[AGGREGATE object RENAME] },
OBJECT_TYPE_FUNCTION => ->(_node) { %w[FUNCTION object RENAME] }
}.freeze
end
end
end
1 change: 1 addition & 0 deletions lib/pg_query/node_types.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class PgQuery
CONSTRAINT = 'Constraint'.freeze
COPY_STMT = 'CopyStmt'.freeze
CREATE_CAST_STMT = 'CreateCastStmt'.freeze
CREATE_DOMAIN_STMT = 'CreateDomainStmt'.freeze
CREATE_ENUM_STMT = 'CreateEnumStmt'.freeze
CREATE_FUNCTION_STMT = 'CreateFunctionStmt'.freeze
CREATE_RANGE_STMT = 'CreateRangeStmt'.freeze
Expand Down
104 changes: 103 additions & 1 deletion spec/lib/pg_query/deparse_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,22 @@
end
end

context 'CREATE DOMAIN' do
context 'with check' do
let(:query) do
"""
CREATE DOMAIN \"us_postal_code\" AS text
CHECK (
\"VALUE\" ~ '^\d{5}$'
OR \"VALUE\" ~ '^\d{5}-\d{4}$'
);
""".strip
end

it { is_expected.to eq oneline_query }
end
end

context 'CREATE FUNCTION' do
# Taken from http://www.postgresql.org/docs/8.3/static/queries-table-expressions.html
context 'with inline function definition' do
Expand Down Expand Up @@ -980,7 +996,7 @@
end

context 'rename' do
let(:query) { 'ALTER TABLE "distributors" RENAME TO suppliers;' }
let(:query) { 'ALTER TABLE "distributors" RENAME TO "suppliers"' }

it { is_expected.to eq oneline_query }
end
Expand All @@ -998,6 +1014,86 @@
end
end

context 'RENAME' do
context 'TRIGGER' do
let(:query) { 'ALTER TRIGGER emp_stamp ON "emp" RENAME TO "emp_track_chgs"' }

it { is_expected.to eq oneline_query }
end

context 'CONVERSION' do
let(:query) { 'ALTER CONVERSION "iso_8859_1_to_utf8" RENAME TO "latin1_to_unicode"' }

it { is_expected.to eq oneline_query }
end

context 'TABLE CONSTRAINT' do
let(:query) { 'ALTER TABLE "distributors" RENAME CONSTRAINT zipchk TO "zip_check"' }

it { is_expected.to eq oneline_query }
end

context 'INDEX' do
let(:query) { 'ALTER INDEX "distributors" RENAME TO "suppliers"' }

it { is_expected.to eq oneline_query }
end

context 'MATERIALIZED VIEW' do
let(:query) { 'ALTER MATERIALIZED VIEW "foo" RENAME TO "bar"' }

it { is_expected.to eq oneline_query }
end

context 'TABLESPACE' do
let(:query) { 'ALTER TABLESPACE index_space RENAME TO "fast_raid"' }

it { is_expected.to eq oneline_query }
end

context 'COLUMN' do
let(:query) { 'ALTER TABLE "distributors" RENAME COLUMN address TO "city"' }

it { is_expected.to eq oneline_query }
end

context 'COLLATION' do
let(:query) { 'ALTER COLLATION "de_DE" RENAME TO "german"' }

it { is_expected.to eq oneline_query }
end

context 'TYPE' do
let(:query) { 'ALTER TYPE "electronic_mail" RENAME TO "email"' }

it { is_expected.to eq oneline_query }
end

context 'DOMAIN CONSTRAINT' do
let(:query) { 'ALTER DOMAIN "zipcode" RENAME CONSTRAINT zipchk TO "zip_check"' }

it { is_expected.to eq oneline_query }
end

context 'AGGREGATE' do
let(:query) { 'ALTER AGGREGATE "myavg"(int) RENAME TO "my_average"' }

it { is_expected.to eq oneline_query }
end

context 'FUNCTION' do
let(:query) { 'ALTER FUNCTION "sqrt"(int) RENAME TO "square_root"' }

it { is_expected.to eq oneline_query }
end

context 'RULE' do
let(:query) { 'ALTER RULE notify_all ON "emp" RENAME TO "notify_me"' }

it { is_expected.to eq oneline_query }
end
end

context 'TRANSACTION' do
context 'BEGIN' do
let(:query) { 'BEGIN' }
Expand Down Expand Up @@ -1075,6 +1171,12 @@
end

context 'VIEWS' do
context 'rename view' do
let(:query) { 'ALTER VIEW "foo" RENAME TO "bar"' }

it { is_expected.to eq query }
end

context 'with check option' do
let(:query) { 'CREATE OR REPLACE TEMPORARY VIEW view_a AS SELECT * FROM a(1) WITH CASCADED CHECK OPTION' }

Expand Down