require 'spec_helper' require 'flydata/parser_provider' describe 'MysqlAlterTableParser' do before do #load parser @parser_class = Mysql::ParserProvider.parser(:mysql, :mysql_alter_table) end let(:query) { "" } let(:parser) { @parser_class.new } subject { parser.parse(query).tree } describe '#parse' do context 'with alter table add column syntax' do context 'with normal alter table query' do let(:query) { "alter table test_table add column value varchar(26)" } it do expect(subject).to eq({ type: :alter_table, table_name: "test_table", actions: [{ action: :add_column, column: "value", type: "varchar(78)", query: "add column value varchar(26)" }] }) end end context 'with different column types' do context 'with longblob' do let(:query) { "alter table test_table add column value longblob" } it do expect(subject).to eq({ type: :alter_table, table_name: "test_table", actions: [{ action: :add_column, column: "value", type: "varbinary(4294967295)", query: "add column value longblob" }] }) end end end context 'without the keyword "COLUMN"' do let(:query) { "alter table test_table add value varchar(26)" } it do expect(subject).to eq({ type: :alter_table, table_name: "test_table", actions: [{ action: :add_column, column: "value", type: "varchar(78)", query: "add value varchar(26)" }] }) end end context 'with multiple columns' do shared_examples 'test result hash' do it do expect(subject).to eq({ type: :alter_table, table_name: "test_table", actions: [{ action: :add_column, column: "value1", type: "varchar(78)", query: "#{expected_query}" },{ action: :add_column, column: "value2", type: "varchar(78)", query: "#{expected_query}" }] }) end end context 'with spaces before opening bracket' do let(:query) { "alter table test_table add column (value1 varchar(26), value2 varchar(26))" } let(:expected_query) {"add column (value1 varchar(26), value2 varchar(26))"} include_examples 'test result hash' end context 'without spaces before opening bracket' do let(:query) { "alter table test_table add column(value1 varchar(26), value2 varchar(26))" } let(:expected_query) {"add column(value1 varchar(26), value2 varchar(26))"} include_examples 'test result hash' end end context 'with multiple columns and without the keyword "COLUMN"' do let(:query) { "alter table test_table add ( value1 varchar(26), value2 varchar(26) )" } it do expect(subject).to eq({ type: :alter_table, table_name: "test_table", actions: [{ action: :add_column, column: "value1", type: "varchar(78)", query: "add ( value1 varchar(26), value2 varchar(26) )" },{ action: :add_column, column: "value2", type: "varchar(78)", query: "add ( value1 varchar(26), value2 varchar(26) )" }] }) end end context 'with multiple columns that have more detailed definitions,without the keyword "COLUMN"' do let(:query) { "alter table test_table add (value1 varchar(26) not null default 'flydata', value2 int auto_increment)" } it do expect(subject).to eq({ type: :alter_table, table_name: "test_table", actions: [{ action: :add_column, column: "value1", type: "varchar(78)", not_null: true, default: "flydata", query: "add (value1 varchar(26) not null default 'flydata', value2 int auto_increment)" },{ action: :add_column, column: "value2", type: "int4(11)", auto_increment: true, query: "add (value1 varchar(26) not null default 'flydata', value2 int auto_increment)" }] }) end end context 'with backticks' do let(:query) { "alter table `test_table` add (`value1` varchar(26) not null default 'flydata', `value2` int auto_increment)" } it do expect(subject).to eq({ type: :alter_table, table_name: "test_table", actions: [{ action: :add_column, column: "value1", type: "varchar(78)", not_null: true, default: "flydata", query: "add (`value1` varchar(26) not null default 'flydata', `value2` int auto_increment)" },{ action: :add_column, column: "value2", type: "int4(11)", auto_increment: true, query: "add (`value1` varchar(26) not null default 'flydata', `value2` int auto_increment)" }] }) end end context 'with after option' do let(:query) { "alter table test_table add column value varchar(26) after id" } it do expect(subject).to eq({ type: :alter_table, table_name: "test_table", actions: [{ action: :add_column, column: "value", type: "varchar(78)", after: 'id', query: "add column value varchar(26) after id" }] }) end end shared_examples "generating a flydata record for the given query" do let(:after_column_name) { 'id' } let(:actions) { %Q|add column value varchar(26) comment 'Ignorable comment' after #{after_column}| } let(:query) { %Q|alter table `test_table` #{actions}| } it do expect(subject).to eq({ type: :alter_table, table_name: "test_table", actions: [{ action: :add_column, column: "value", type: "varchar(78)", after: after_column_name, query: actions }] }) end end context 'with after option, quoted after column name' do let(:after_column) { "`#{after_column_name}`" } it_behaves_like "generating a flydata record for the given query" end context 'with after option, backticks only on table name and a comment' do let(:after_column) { after_column_name } it_behaves_like "generating a flydata record for the given query" end context 'with first option' do let(:query) { "alter table test_table add column value varchar(26) first" } it do expect(subject).to eq({ type: :alter_table, table_name: "test_table", actions: [{ action: :add_column, column: "value", type: "varchar(78)", position: :first, query: "add column value varchar(26) first" }] }) end end context 'with first, unique' do let(:query) { "alter table test_table add column value varchar(26) UNIQUE first" } it do expect(subject).to eq({ type: :alter_table, table_name: "test_table", actions: [{ action: :add_column, column: "value", type: "varchar(78)", unique: true, position: :first, query: "add column value varchar(26) UNIQUE first" }] }) end end context "with default option" do shared_examples "a query parser parsing a query adding a column with a default value" do let(:query) { "alter table test_table add column col #{data_type} default #{value}" } it do is_expected.to eq({ type: :alter_table, table_name: "test_table", actions: [{ action: :add_column, column: "col", type: expected_data_type, default: expected_value, query: "add column col #{data_type} default #{value}" }] }) end end context 'for timestamp column' do let(:data_type) { "timestamp" } let(:expected_data_type) { "datetime" } context 'taking current_timestamp' do let(:value) { "current_timestamp" } let(:expected_value) { "current_timestamp" } it_behaves_like "a query parser parsing a query adding a column with a default value" end context 'taking localtime' do let(:value) { "localtime" } let(:expected_value) { "current_timestamp" } it_behaves_like "a query parser parsing a query adding a column with a default value" end context 'taking localtimestamp' do let(:value) { "localtimestamp" } let(:expected_value) { "current_timestamp" } it_behaves_like "a query parser parsing a query adding a column with a default value" end context 'taking now()' do let(:value) { "now()" } let(:expected_value) { "current_timestamp" } it_behaves_like "a query parser parsing a query adding a column with a default value" end end context 'for integer column' do let(:data_type) { "int(11)" } let(:expected_data_type) { "int4(11)" } context 'taking a positive number without sign' do let(:value) { "1" } let(:expected_value) { "1" } it_behaves_like "a query parser parsing a query adding a column with a default value" end context 'taking a positive number' do let(:value) { "+1" } let(:expected_value) { "1" } it_behaves_like "a query parser parsing a query adding a column with a default value" end context 'taking a negative number' do let(:value) { "-1" } let(:expected_value) { "-1" } it_behaves_like "a query parser parsing a query adding a column with a default value" end context 'taking a negative number string' do let(:value) { %Q|'-1'| } let(:expected_value) { "-1" } it_behaves_like "a query parser parsing a query adding a column with a default value" end context 'taking null' do let(:value) { %Q|NULL| } let(:expected_value) { nil } it_behaves_like "a query parser parsing a query adding a column with a default value" end end context 'for decimal column' do let(:data_type) { "decimal(10,2)" } let(:expected_data_type) { "numeric(10,2)" } context 'taking a positive number without sign' do let(:value) { "2.4" } let(:expected_value) { "2.4" } it_behaves_like "a query parser parsing a query adding a column with a default value" end context 'taking a positive number with sign' do let(:value) { "+0.4" } let(:expected_value) { "0.4" } it_behaves_like "a query parser parsing a query adding a column with a default value" end context 'taking a positive number no integer part' do let(:value) { ".05" } let(:expected_value) { "0.05" } it_behaves_like "a query parser parsing a query adding a column with a default value" end context 'taking a negative number' do let(:value) { "-20.43" } let(:expected_value) { "-20.43" } it_behaves_like "a query parser parsing a query adding a column with a default value" end context 'taking a negative number no integer part' do let(:value) { "-.43" } let(:expected_value) { "-0.43" } it_behaves_like "a query parser parsing a query adding a column with a default value" end end context 'for varchar column' do let(:data_type) { "varchar(10)" } let(:expected_data_type) { "varchar(30)" } context 'taking null' do let(:value) { "NULL" } let(:expected_value) { nil } it_behaves_like "a query parser parsing a query adding a column with a default value" end context 'taking empty string' do let(:value) { %Q|''| } let(:expected_value) { "" } it_behaves_like "a query parser parsing a query adding a column with a default value" end context 'taking a string with spaces' do let(:value) { %Q|'0 0 0 0 0 0 0'| } let(:expected_value) { "0 0 0 0 0 0 0" } it_behaves_like "a query parser parsing a query adding a column with a default value" end context 'taking a backslash' do let(:value) { %q|'\\\\'| } let(:expected_value) { %q|\\\\| } it_behaves_like "a query parser parsing a query adding a column with a default value" end context 'taking a string with special characters' do let(:value) { %q|'https://flydata.com $ \\\\\'\"\n\t'| } let(:expected_value) { %q|https://flydata.com $ \\\\\'\"\n\t| } it_behaves_like "a query parser parsing a query adding a column with a default value" end end end context 'with null option' do let(:length) { 45 } let(:query) { "alter table test_table add column extra varchar(#{length}) null" } it do expect(subject).to eq({ type: :alter_table, table_name: "test_table", actions: [{ action: :add_column, column: "extra", type: "varchar(#{length * 3})", query: "add column extra varchar(#{length}) null" }] }) end end end context 'with alter table drop column syntax' do let(:query) { "alter table test_table drop column value" } it do expect(subject).to eq( type: :alter_table, table_name: "test_table", actions: [{ action: :drop_column, column: "value", query: "drop column value" }]) end end context 'without column keyword' do let(:query) { "alter table test_table drop value" } it do expect(subject).to eq( type: :alter_table, table_name: "test_table", actions: [{ action: :drop_column, column: "value", query: "drop value" }]) end end context 'when column_name is wrapped with backquote' do let(:query) { "alter table test_table drop `value`" } it do expect(subject).to eq( type: :alter_table, table_name: "test_table", actions: [{ action: :drop_column, column: "value", query: "drop `value`" }]) end end context 'when table_name is wrapped with backquote' do let(:query) { "alter table `test_table` drop value" } it do expect(subject).to eq( type: :alter_table, table_name: "test_table", actions: [{ action: :drop_column, column: "value", query: "drop value" }]) end end context 'when keywords are capitalized' do let(:query) { "ALTER TABLE `test_table` DROP value" } it do expect(subject).to eq( type: :alter_table, table_name: "test_table", actions: [{ action: :drop_column, column: "value", query: "DROP value" }]) end end context 'when table_name includes schema' do let(:query) { "alter table test_schema.test_table drop value" } it do expect(subject).to eq( type: :alter_table, schema_name: "test_schema", table_name: "test_table", actions: [{ action: :drop_column, column: "value", query: "drop value" }]) end end context 'when table_name includes schema with quotes' do let(:query) { "alter table `test_schema`.`test_table` drop `value`" } it do expect(subject).to eq( type: :alter_table, schema_name: "test_schema", table_name: "test_table", actions: [{ action: :drop_column, column: "value", query: "drop `value`" }]) end end context 'with multiple alter specs' do context 'with add column and drop column' do let(:query) { "alter table test_table add column value varchar(26) after id, drop old_value" } it do expect(subject).to eq({ type: :alter_table, table_name: "test_table", actions: [{ action: :add_column, column: "value", type: "varchar(78)", after: 'id', query: "add column value varchar(26) after id" },{ action: :drop_column, column: "old_value", query: "drop old_value" }] }) end end end let(:query) { "ALTER TABLE `test_schema`.`test_table` #{alter_table_action}" } shared_examples "a parser parsing a single action alter table query" do it do is_expected.to eq({ type: :alter_table, schema_name: "test_schema", table_name: "test_table", actions: [action_hash] }) end end shared_examples "a parser raising an error" do it do expect { subject }.to raise_error end end shared_examples "a parser parsing a nonbreaking query" do let(:action_hash) { { action: action, support_level: :nonbreaking, query: alter_table_action, } } it_behaves_like "a parser parsing a single action alter table query" end shared_examples "a parser parsing a breaking query" do let(:action_hash) { { action: action, query: alter_table_action, } } it_behaves_like "a parser parsing a single action alter table query" end shared_examples "a debug parser" do it do puts query result = parser.parse(query) p result puts "FAILURE: #{parser.failure_reason}" end end shared_examples "test normal key options" do |*examples| context "with normal key options" do context "key_block_size" do let(:normal_key_options) { " KEY_BLOCK_SIZE = 102400" } it_behaves_like *examples end context "key_block_size with no space" do let(:normal_key_options) { " KEY_BLOCK_SIZE=102400" } it_behaves_like *examples end context "key_block_size no equal sign" do let(:normal_key_options) { " KEY_BLOCK_SIZE 102400" } it_behaves_like *examples end context "using" do let(:normal_key_options) { " using btree" } it_behaves_like *examples end context "comment" do let(:normal_key_options) { " comment 'Hello World!'" } it_behaves_like *examples end context "key_block_size and comment" do let(:normal_key_options) { " KEY_BLOCK_SIZE 102400 comment 'Hello World!'" } it_behaves_like *examples end context "none" do it_behaves_like *examples do let(:normal_key_options) { "" } end end end end shared_examples "test key list without order" do |*examples| context "with a single key" do it_behaves_like *examples do let(:key_list) { " testKey" } let(:expected_key_list) { [ "testKey" ] } end end context "with a quoted single key" do it_behaves_like *examples do let(:key_list) { " `testKey`" } let(:expected_key_list) { [ "testKey" ] } end end context "with multiple keys" do it_behaves_like *examples do let(:key_list) { " testKey1, testKey2" } let(:expected_key_list) { [ "testKey1", "testKey2" ] } end end end shared_examples "test key list" do |*examples| include_examples "test key list without order", *examples context "with multiple keys with order" do it_behaves_like *examples do let(:key_list) { " testKey1 ASC, testKey2 desc" } let(:expected_key_list) { [ "testKey1 asc", "testKey2 desc" ] } end end end shared_examples "test constraint keyword" do |*examples| context "with constraint keyword" do context "with backticked constraint name" do it_behaves_like *examples do let(:constraint_keyword) { " CONSTRAINT `chkName`" } end end context "with constraint name" do it_behaves_like *examples do let(:constraint_keyword) { " CONSTRAINT chkName" } end end context "without constraint name" do it_behaves_like *examples do let(:constraint_keyword) { " CONSTRAINT" } end end end context "without constraint keyword" do it_behaves_like *examples do let(:constraint_keyword) { "" } end end end shared_examples "test key name" do |*examples| context "with key name" do it_behaves_like *examples do let(:key_name) { " testKey" } end end context "without key name" do it_behaves_like *examples do let(:key_name) { "" } end end context "with quoted key name" do it_behaves_like *examples do let(:key_name) { " `testKey`" } end end end shared_examples "test key or index" do |*examples| context "with key" do it_behaves_like *examples do let(:key_or_index) { " KEY" } end end context "with index" do it_behaves_like *examples do let(:key_or_index) { " index" } end end end shared_examples "test optional key or index" do |*examples| include_examples "test key or index", *examples context "with none" do it_behaves_like *examples do let(:key_or_index) { "" } end end end shared_examples "test using option" do |*examples| context "with BTREE" do it_behaves_like *examples do let(:using_option) { " USING BTREE" } end end context "with hash" do it_behaves_like *examples do let(:using_option) { " using hash" } end end context "with none" do it_behaves_like *examples do let(:using_option) { "" } end end end shared_examples "test identifier combination without ." do |*examples| it_behaves_like "test identifier", *examples do let(:identifier) { " #{ident}" } end end shared_examples "test identifier combination with single ." do |*examples| it_behaves_like "test identifier", *examples do let(:identifier) { " #{ident}.#{ident}" } end end shared_examples "test identifier combination with single . starting with ." do |*examples| it_behaves_like "test identifier", *examples do let(:identifier) { " .#{ident}" } end end shared_examples "test identifier combination with two ." do |*examples| it_behaves_like "test identifier", *examples do let(:identifier) { " #{ident}.#{ident}.#{ident}" } end end shared_examples "test identifier combination with two . starting with ." do |*examples| it_behaves_like "test identifier", *examples do let(:identifier) { " .#{ident}.#{ident}" } end end shared_examples "test field identifier" do |*examples| it_behaves_like "test identifier combination without .", *examples it_behaves_like "test identifier combination with single .", *examples it_behaves_like "test identifier combination with two .", *examples end shared_examples "test identifier" do |*examples| context "unquoted identifier" do it_behaves_like *examples do let(:ident) {"test_ident1"} end end context "quoted identifier" do it_behaves_like *examples do let(:ident) {"`test_ident1`"} end end end shared_examples "test optional to" do |*examples| context "with to" do it_behaves_like *examples do let(:opt_to) {" TO "} end end context "with =" do it_behaves_like *examples do let(:opt_to) {" = "} end end context "with = without spaces" do it_behaves_like *examples do let(:opt_to) {"="} end end context "with AS" do it_behaves_like *examples do let(:opt_to) {" as "} end end context "with none" do it_behaves_like *examples do let(:opt_to) { "" } end end end shared_examples "test table identifier" do |*examples| it_behaves_like "test identifier combination without .", *examples it_behaves_like "test identifier combination with single .", *examples it_behaves_like "test identifier combination with single . starting with .", *examples end shared_examples "test simple identifier" do |*examples| it_behaves_like "test identifier combination without .", *examples it_behaves_like "test identifier combination with single .", *examples it_behaves_like "test identifier combination with two .", *examples it_behaves_like "test identifier combination with two . starting with .", *examples end shared_examples "test order" do |*examples| context "ascending" do it_behaves_like *examples do let(:order){" ASC"} end end context "descending" do it_behaves_like *examples do let(:order){" desc"} end end context "no order" do it_behaves_like *examples do let(:order){""} end end end shared_examples "test charset" do |*examples| ["CHARACTER SET", "charset"].each do |charset| context "charset is #{charset}" do it_behaves_like *examples do let(:charset){"#{charset}"} end end end end shared_examples "test charsetname" do |*examples| ["default","binary","utf8","latin1"].each do |charsetname| context "charsetname is #{charsetname}" do it_behaves_like *examples do let(:charsetname){"#{charsetname}"} end end end end shared_examples "test collate" do |*examples| ["collate default", "collate utf8_general_ci", "collate latin1_bin"].each do |collate| context "collate is #{collate}" do it_behaves_like *examples do let(:collate){"#{collate}"} end end end end shared_examples "test collate with equals" do |*examples| ["collate=default", "collate = utf8_general_ci"].each do |collate| context "collate is #{collate}" do it_behaves_like *examples do let(:collate){"#{collate}"} end end end end shared_examples "test optional default" do |*examples| context "with default" do it_behaves_like *examples do let(:default){"default "} end end context "without default" do it_behaves_like *examples do let(:default){""} end end end shared_examples "test optional no_write_to_binlog" do |*examples| context "with no_write_to_binlog" do it_behaves_like *examples do let(:no_write_to_binlog){" no_write_to_binlog"} end end context "with local" do it_behaves_like *examples do let(:no_write_to_binlog){" LOCAL"} end end context "without local or no_write_to_binlog flag" do it_behaves_like *examples do let(:no_write_to_binlog){""} end end end shared_examples "test partition names" do |*examples| context "ALL" do it_behaves_like *examples do let(:partition_names){"ALL"} end end context "ALL lower case" do it_behaves_like *examples do let(:partition_names){"all"} end end context "single partition" do it_behaves_like *examples do let(:partition_names){"p0"} end end context "multiple partitions" do it_behaves_like *examples do let(:partition_names){"p0,P1, p2"} end end end shared_examples "test check types" do |*examples| ["quick", "FAST", "medium", "EXTENDED", "CHANGED", "for upgrade"].each do |check_type| context "check_type is #{check_type}" do it_behaves_like *examples do let(:check_type) {check_type} end end end context "multiple check types" do it_behaves_like *examples do let(:check_type) {"fast for upgrade"} end end end shared_examples "test repair types" do |*examples| ["quick", "EXTENDED", "use_frm"].each do |repair_type| context "repair_type is #{repair_type}" do it_behaves_like *examples do let(:repair_type) {repair_type} end end end context "multiple repair types" do it_behaves_like *examples do let(:repair_type) {"quick USE_FRM"} end end end shared_examples "test optional validation" do |*examples| context "with validation" do it_behaves_like *examples do let(:validation){" with validation"} end end context "without validation" do it_behaves_like *examples do let(:validation){" WITHOUT VALIDATION"} end end context "none" do it_behaves_like *examples do let(:validation){""} end end end shared_examples "test optional equal" do |*examples| context "with EQ" do it_behaves_like *examples do let(:equal){"="} end end context "with EQ and a space before it" do it_behaves_like *examples do let(:equal){" ="} end end context "with SET_VAR" do it_behaves_like *examples do let(:equal){":="} end end context "with none" do it_behaves_like *examples do let(:equal){""} end end end shared_examples "test signed literal" do |*examples| context "number literal" do it_behaves_like *examples do let(:signed_literal){"100"} end end context "signed number literal" do it_behaves_like *examples do let(:signed_literal){"100"} end end context "signed number literal within quotes" do it_behaves_like *examples do let(:signed_literal){"'+100'"} end end context "null" do it_behaves_like *examples do let(:signed_literal){"NULL"} end end context "boolean" do it_behaves_like *examples do let(:signed_literal){"TRUE"} end end context "quoted string" do it_behaves_like *examples do let(:signed_literal){"'Hello World!'"} end end end shared_examples "test optional reference list" do |*examples| context "with no references" do it_behaves_like *examples do let(:opt_ref_list) {""} end end context "with single reference" do it_behaves_like *examples do let(:opt_ref_list) {"(id)"} end end context "with multiple references" do it_behaves_like *examples do let(:opt_ref_list) {" (id_1, id2)"} end end end shared_examples "test optional match clause" do |*examples| context "no match clause" do it_behaves_like *examples do let(:opt_match_clause) {""} end end context "match full" do it_behaves_like *examples do let(:opt_match_clause) {" MATCH FULL"} end end context "match partial" do it_behaves_like *examples do let(:opt_match_clause) {" match PARTIAL"} end end end shared_examples "test optional on update delete" do |*examples| context "no on update delete clause" do it_behaves_like *examples do let(:opt_on_update_delete) {""} end end context "on update and on delete" do it_behaves_like "test delete option", *examples do let(:opt_on_update_delete) {" on update #{delete_option} on delete #{delete_option}"} end end context "on update only" do it_behaves_like "test delete option", *examples do let(:opt_on_update_delete) {" ON UPDATE #{delete_option}"} end end end shared_examples "test delete option" do |*examples| context "restrict" do it_behaves_like *examples do let(:delete_option) {"restrict"} end end context "set null" do it_behaves_like *examples do let(:delete_option) {"SET NULL"} end end end context 'add index' do let(:action) { :add_index } context "with a simple add index" do let(:alter_table_action) { "ADD INDEX `test` (`t_now`)" } it_behaves_like "a parser parsing a nonbreaking query" end context "with a simple add index and without space before the opening bracket" do let(:alter_table_action) { "ADD INDEX `test`(`t_now`)" } it_behaves_like "a parser parsing a nonbreaking query" end context "with a simple add index with order" do let(:alter_table_action) { "ADD INDEX `test` (`t_now` ASC)" } it_behaves_like "a parser parsing a nonbreaking query" end context "with an add index with multiple fields" do let(:key_list) { "`t_now` ASC, `t_timestamp` DESC" } let(:alter_table_action) { "ADD INDEX `test` (#{key_list})" } it_behaves_like "a parser parsing a nonbreaking query" context "with spaces around key list" do let(:key_list) { " `t_now` ASC, `t_timestamp` DESC " } it_behaves_like "a parser parsing a nonbreaking query" end context "with a field without order" do let(:key_list) { " `t_now` ASC, `t_timestamp`" } it_behaves_like "a parser parsing a nonbreaking query" end end context "with an add index with no index name" do let(:alter_table_action) { "ADD INDEX (`t_now` ASC)" } it_behaves_like "a parser parsing a nonbreaking query" end context "with an add index with USING" do let(:using_option) { "USING BTREE" } let(:alter_table_action) { "ADD INDEX `test` #{using_option} (`t_now` ASC)" } it_behaves_like "a parser parsing a nonbreaking query" context "without index name" do let(:alter_table_action) { "ADD INDEX #{using_option} (`t_now` ASC)" } it_behaves_like "a parser parsing a nonbreaking query" end context "without index name and without space before the opening bracket" do let(:alter_table_action) { "ADD INDEX #{using_option}(`t_now` ASC)" } it_behaves_like "a parser parsing a nonbreaking query" end context "with hash" do let(:using_option) { "USING hash" } it_behaves_like "a parser parsing a nonbreaking query" end end context "with normal key options" do let(:alter_table_action) { "ADD INDEX (`t_now` ASC)#{normal_key_options}" } it_behaves_like "test normal key options", "a parser parsing a nonbreaking query" end end context "fulltext or spatial" do let(:action) { :add_index } shared_examples "test all permutations" do |examples| context "key_or_index" do shared_examples "a parser parsing a nonbreaking query with key options" do let(:alter_table_action) { "ADD #{fulltext_or_spatial} #{key_or_index} #{key_name} (test_column)#{key_options}" } context "no key option" do it_behaves_like "a parser parsing a nonbreaking query" do let(:key_options) { "" } end end context "key_block_size" do it_behaves_like "a parser parsing a nonbreaking query" do let(:key_options) { " KEY_BLOCK_SIZE 2048" } end end context "comment" do it_behaves_like "a parser parsing a nonbreaking query" do let(:key_options) { " COMMENT 'Hello World!'" } end end context "comment and key_block_size" do it_behaves_like "a parser parsing a nonbreaking query" do let(:key_options) { " KEY_BLOCK_SIZE = 1024 COMMENT 'Hello World!'" } end end end shared_examples "a parser parsing a nonbreaking query with key options fulltext" do it_behaves_like "a parser parsing a nonbreaking query with key options" let(:alter_table_action) { "ADD #{fulltext_or_spatial} #{key_or_index} #{key_name} (test_column)#{key_options}" } context "with parser" do it_behaves_like "a parser parsing a nonbreaking query" do let(:key_options) { " WITH PARSER test_parser" } end end end shared_examples "a parser parsing a query with either key or index" do |*examples| let(:alter_table_action) { "ADD #{fulltext_or_spatial} #{key_or_index} #{key_name} (test_column)" } context "no key name" do it_behaves_like *examples do let(:key_name) { "" } end end context "simple key name" do it_behaves_like *examples do let(:key_name) { "test_key" } end end context "quoted key name" do it_behaves_like *examples do let(:key_name) { "`test_key`" } end end end it_behaves_like "test optional key or index", "a parser parsing a query with either key or index", examples end end context "with fulltext" do it_behaves_like "test all permutations", "a parser parsing a nonbreaking query with key options fulltext" do let(:fulltext_or_spatial) { "FULLTEXT" } end end context "with spatial" do it_behaves_like "test all permutations", "a parser parsing a nonbreaking query with key options" do let(:fulltext_or_spatial) { "SPATIAL" } end end end context "check constraint" do let(:action) { :add_check_constraint } let(:alter_table_action) { "ADD#{constraint_keyword} CHECK (#{constraint_expr})" } shared_examples "test constraint expr" do context "with constraint expr" do it_behaves_like "test constraint keyword", "a parser parsing a nonbreaking query" do let(:constraint_expr) { "P_Id>0 AND City='Sandnes'" } end end end context "test all" do it_behaves_like "test constraint expr" end end context "unique constraint" do let(:action) { :add_unique_constraint } let(:alter_table_action) { "ADD#{constraint_keyword} UNIQUE#{key_or_index}#{key_name}#{using_option} (#{key_list})#{normal_key_options}" } it_behaves_like "test constraint keyword", "test optional key or index", "test key name", "test using option", "test key list", "test normal key options", # "a debug parser" "a parser parsing a nonbreaking query" end context "primary key constraint" do let(:alter_table_action) { "ADD#{constraint_keyword} PRIMARY KEY#{key_name}#{using_option} (#{key_list})#{normal_key_options}" } let(:action) { :add_primary_key_constraint } let(:action_hash) { { action: action, keys: expected_key_list, query: alter_table_action, } } it_behaves_like "test constraint keyword", "test key name", "test using option", "test key list", "test normal key options", # "a debug parser" "a parser parsing a single action alter table query" end context 'add foreign key' do let(:action) { :add_foreign_key_constraint } let(:alter_table_action) { "add#{constraint_keyword} foreign key#{key_name}(#{key_list}) references employees#{opt_ref_list}#{opt_match_clause}#{opt_on_update_delete}"} it_behaves_like "test constraint keyword", "test key name", "test key list without order", "test optional reference list", "test optional match clause", "test optional on update delete", "a parser parsing a nonbreaking query" end context "enable keys" do let(:alter_table_action) { "ENABLE KEYS" } let(:action) { :enable_keys } it_behaves_like "a parser parsing a nonbreaking query" end context "disable keys" do let(:alter_table_action) { "DISABLE KEYS" } let(:action) { :disable_keys } it_behaves_like "a parser parsing a nonbreaking query" end context "drop primary key" do let(:alter_table_action) { "DROP PRIMARY KEY" } let(:action) { :drop_primary_key } it_behaves_like "a parser parsing a breaking query" end context "drop foreign key" do let(:alter_table_action) { "DROP FOREIGN KEY#{identifier}" } let(:action) { :drop_foreign_key } it_behaves_like "test field identifier", "a parser parsing a nonbreaking query" end context "drop key or index" do let(:alter_table_action) { "DROP#{key_or_index}#{identifier}" } let(:action) { "drop_#{key_or_index.strip.downcase}".to_sym } it_behaves_like "test key or index", "test field identifier", "a parser parsing a nonbreaking query" end context "rename table" do let(:alter_table_action) { "rename#{opt_to}#{identifier}" } let(:action) { :rename_table } it_behaves_like "test optional to", "test table identifier", "a parser parsing a breaking query" context "exception cases" do context "with as followed by quoted identifier and no space in between" do let(:alter_table_action) { "rename as`test_ident1`" } #strange, but valid it_behaves_like "a parser parsing a breaking query" end context "with to followed by quoted identifier and no space in between" do let(:alter_table_action) { "rename to`test_ident1`" } it_behaves_like "a parser parsing a breaking query" end context "no space between rename and to" do let(:alter_table_action) { "renameto `test_ident1`" } it_behaves_like "a parser raising an error" end context "no space between to and table name" do let(:alter_table_action) { "rename totest_ident1" } describe "'totest_ident1' is treated as a table name" do it_behaves_like "a parser parsing a breaking query" end end end end context "rename key or index" do let(:alter_table_action) { "rename#{key_or_index}#{identifier} TO#{identifier}" } let(:action) { :rename_key_or_index } it_behaves_like "test key or index", "test field identifier", "a parser parsing a nonbreaking query" end context "order by" do let(:action) { :order_table } context "with single column" do let(:alter_table_action) { "order by#{identifier}#{order}" } it_behaves_like "test simple identifier", "test order", "a parser parsing a breaking query" end context "with multiple columns" do let(:alter_table_action) { "order by#{identifier}#{order},#{identifier}#{order},#{identifier}#{order}" } it_behaves_like "test simple identifier", "test order", "a parser parsing a breaking query" end end context "convert to charset" do let(:action) { :convert_charset } context "test with collate" do let(:alter_table_action) { "convert to #{charset} #{charsetname} #{collate}" } it_behaves_like "test charset", "test charsetname", "test collate", "a parser parsing a breaking query" end context "test without collate" do let(:alter_table_action) { "convert to #{charset} #{charsetname}" } it_behaves_like "test charset", "test charsetname", "a parser parsing a breaking query" end end context "default charset" do let(:action) { :default_charset } context "with collate" do let(:alter_table_action) { "#{default}#{charset}=#{charsetname} #{collate}" } it_behaves_like "test optional default", "test charset", "test charsetname", "test collate", "a parser parsing a breaking query" it_behaves_like "test optional default", "test charset", "test charsetname", "test collate with equals", "a parser parsing a breaking query" end context "without collate" do let(:alter_table_action) { "#{default}#{charset} #{charsetname}" } it_behaves_like "test optional default", "test charset", "test charsetname", "a parser parsing a breaking query" end context "without charset" do #The doc at http://dev.mysql.com/doc/refman/5.6/en/alter-table.html does not indicate that #charset is optional. But this works (tested on mysql 5.5.38) let(:alter_table_action) { "#{default}#{collate}" } it_behaves_like "test optional default","test collate", "a parser parsing a breaking query" it_behaves_like "test optional default","test collate with equals", "a parser parsing a breaking query" end end context "force" do let(:alter_table_action) { "force" } let(:action) { :force } it_behaves_like "a parser parsing a nonbreaking query" end context "discard tablespace" do let(:alter_table_action) { "discard tablespace" } let(:action) { :discard_tablespace } it_behaves_like "a parser parsing a breaking query" end context "import tablespace" do let(:alter_table_action) { "import tablespace" } let(:action) { :import_tablespace } it_behaves_like "a parser parsing a breaking query" end context "add partition" do let(:action) { :add_partition } context "simple definition with spaces" do let(:alter_table_action) { "add partition#{no_write_to_binlog} (PARTITION p2)" } it_behaves_like "test optional no_write_to_binlog", "a parser parsing a nonbreaking query" end context "complex partition definition without spaces" do let(:alter_table_action) { "ADD partition#{no_write_to_binlog}(PARTITION p2 VALUES LESS THAN (800))" } it_behaves_like "test optional no_write_to_binlog", "a parser parsing a nonbreaking query" end end context "drop partition" do let(:action) { :drop_partition } context "drop single partition" do let(:alter_table_action) { "DROP partition p0" } it_behaves_like "a parser parsing a nonbreaking query" end context "drop multiple partitions" do context "upcase and spaces between partition names" do let(:alter_table_action) { "DROP PARTITION p0, p1" } it_behaves_like "a parser parsing a nonbreaking query" end context "lowercase and no spaces between partition names" do let(:alter_table_action) { "drop partition p0,p1,p2" } it_behaves_like "a parser parsing a nonbreaking query" end end end context "rebuild, optimize, analyze partitions" do ["rebuild", "optimize", "analyze"].each do |op| context "#{op} partition" do let(:action) { "#{op}_partition".to_sym } context "with upcase op" do let(:alter_table_action) { "#{op.upcase} PARTITION#{no_write_to_binlog} #{partition_names}" } it_behaves_like "test partition names", "test optional no_write_to_binlog", "a parser parsing a nonbreaking query" end context "with lowercase op" do let(:alter_table_action) { "#{op} partition#{no_write_to_binlog} #{partition_names}" } it_behaves_like "test partition names", "test optional no_write_to_binlog", "a parser parsing a nonbreaking query" end end end end context "check partition" do let(:action) { :check_partition } context "without check_type" do let(:alter_table_action) { "CHECK PARTITION #{partition_names}" } it_behaves_like "test partition names", "a parser parsing a nonbreaking query" end context "with check type" do let(:alter_table_action) { "check partition #{partition_names} #{check_type}" } it_behaves_like "test check types", "test partition names", "a parser parsing a nonbreaking query" end end context "repair partition" do let(:action) { :repair_partition } context "without repair_type" do let(:alter_table_action) { "REPAIR PARTITION#{no_write_to_binlog} #{partition_names}" } it_behaves_like "test partition names", "test optional no_write_to_binlog", "a parser parsing a nonbreaking query" end context "with repair type" do let(:alter_table_action) { "repair partition#{no_write_to_binlog} #{partition_names} #{repair_type}" } it_behaves_like "test repair types", "test partition names", "test optional no_write_to_binlog", "a parser parsing a nonbreaking query" end end context "coalesce partition" do let(:action) { :coalesce_partition } context "single digit partition number" do let(:alter_table_action) { "coalesce partition#{no_write_to_binlog} 1" } it_behaves_like "test optional no_write_to_binlog", "a parser parsing a nonbreaking query" end context "double digit partition number" do let(:alter_table_action) { "COALESCE PARTITION#{no_write_to_binlog} 22" } it_behaves_like "test optional no_write_to_binlog", "a parser parsing a nonbreaking query" end end context "truncate partition" do let(:action) { :truncate_partition } let(:alter_table_action) { "TRUNCATE PARTITION #{partition_names}" } it_behaves_like "test partition names", "a parser parsing a nonbreaking query" end context "reorganize partition" do let(:action) { :reorganize_partition } context "simple definition without partition names and partition definition" do let(:alter_table_action) { "reorganize partition#{no_write_to_binlog}" } it_behaves_like "test optional no_write_to_binlog", "a parser parsing a nonbreaking query" end context "complex partition definition" do let(:alter_table_action) { "REORGANIZE PARTITION#{no_write_to_binlog} p0 INTO (PARTITION n0 VALUES LESS THAN (1960))" } it_behaves_like "test optional no_write_to_binlog", "a parser parsing a nonbreaking query" end end context "exchange partition" do let(:action) { :exchange_partition } let(:alter_table_action) { "EXCHANGE PARTITION #{ident} with table#{identifier}#{validation}" } it_behaves_like "test table identifier", "test optional validation", "a parser parsing a nonbreaking query" end context "discard and import partition tablespace" do ["discard", "import"].each do |op| context "#{op}" do let(:action) {"#{op}_partition_tablespace".to_sym} let(:alter_table_action) {"#{op} PARTITION #{partition_names} TABLESPACE"} it_behaves_like "test partition names", "a parser parsing a nonbreaking query" end end end context "remove partitioning" do let(:action) {:remove_partitioning} let(:alter_table_action) { "REMOVE PARTITIONING" } it_behaves_like "a parser parsing a nonbreaking query" end ## Change and modify column related tests shared_examples "test optional column" do |*examples| context "with column" do it_behaves_like *examples do let(:column) {" COLUMN"} end end context "without column" do it_behaves_like *examples do let(:column) { "" } end end end shared_examples "test position" do |*examples| context "with after" do it_behaves_like *examples do let(:position) {" after test_name"} let(:position_hash){ { after: "test_name" } } end end context "with first" do it_behaves_like *examples do let(:position) {" first"} let(:position_hash){ { position: :first } } end end context "with none" do it_behaves_like *examples do let(:position) {""} let(:position_hash){ {} } end end end shared_examples "test not null" do |*examples| context "with not null option" do it_behaves_like *examples do let(:not_null) {" not null"} let(:not_null_hash){ { not_null: true } } end end context "without not null option" do it_behaves_like *examples do let(:not_null) {""} let(:not_null_hash){ {} } end end end shared_examples "test default for timestamp" do |*examples| context "timestamp" do it_behaves_like *examples do let(:default) {" default current_timestamp"} let(:default_hash){ { default: "current_timestamp" } } end end context "timestamp" do it_behaves_like *examples do let(:default) {" default localtime"} let(:default_hash){ { default: "current_timestamp" } } end end context "none" do it_behaves_like *examples do let(:default) {""} let(:default_hash){ {} } end end end shared_examples "test default for varchar" do |*examples| context "null" do it_behaves_like *examples do let(:default) {" default NULL"} let(:default_hash){ { default: nil } } end end context "empty string" do it_behaves_like *examples do let(:default) {" default ''"} let(:default_hash){ { default: "" } } end end context "string with spaces" do it_behaves_like *examples do let(:default) {" default '0 0 0 0 0 0'"} let(:default_hash){ { default: "0 0 0 0 0 0" } } end end context "none" do it_behaves_like *examples do let(:default) {""} let(:default_hash){ {} } end end end shared_examples "test auto increment" do |*examples| context "with auto_increment" do it_behaves_like *examples do let(:auto_increment) {" auto_increment"} let(:auto_increment_hash){ { auto_increment: true } } end end context "without auto_increment" do it_behaves_like *examples do let(:auto_increment) {""} let(:auto_increment_hash){ {} } end end end shared_examples "test comment" do |*examples| context "with a comment" do it_behaves_like *examples do let(:comment) {" COMMENT 'Hello World!'"} end end context "with a double quoted comment" do it_behaves_like *examples do let(:comment) {%q| comment "Hello World!"|} end end context "with a backslash inside double quotes" do it_behaves_like *examples do let(:comment) {%q| comment "\\\\"|} end end context "with special characters" do it_behaves_like *examples do let(:comment) {%q| comment "https://flydata.com $ \\\\'\"\n\t"|} end end context "without a comment" do it_behaves_like *examples do let(:comment) {""} end end end shared_examples "test unique" do |*examples| context "with unique" do it_behaves_like *examples do let(:unique) {" UNIQUE"} let(:unique_hash){ { unique: true } } end end context "without unique" do it_behaves_like *examples do let(:unique) {""} let(:unique_hash){ {} } end end end shared_examples "test column definition" do |*examples| context "varchar" do it_behaves_like "test not null", "test default for varchar", "test comment", *examples do let(:col_def) {"varchar(256)#{not_null}#{default}#{comment}"} let(:col_def_hash) { { type: "varchar(768)" }.merge!(not_null_hash).merge!(default_hash) } end end context "timestamp" do it_behaves_like "test not null", "test default for timestamp", *examples do let(:col_def) {"timestamp#{not_null}#{default}"} let(:col_def_hash) { { type: "datetime" }.merge!(not_null_hash).merge!(default_hash) } end end context "integer" do it_behaves_like "test not null", "test auto increment", "test comment", *examples do let(:col_def) {"int(11)#{not_null}#{auto_increment}#{comment}"} let(:col_def_hash) { { type: "int4(11)" }.merge!(not_null_hash).merge!(auto_increment_hash) } end end context "decimal" do it_behaves_like "test not null", "test unique", *examples do let(:col_def) {"decimal(10,2)#{not_null}#{unique}"} let(:col_def_hash) { { type: "numeric(10,2)" }.merge!(not_null_hash).merge!(unique_hash) } end end end shared_examples "test parser parsing a change column" do let(:action_hash) { change_action_hash.merge!({ action: :change_column, query: alter_table_action }) } it_behaves_like "a parser parsing a single action alter table query" end context 'change column' do context 'change column name' do let(:alter_table_action) { "change#{column} name name1 #{col_def}#{position}" } let(:change_action_hash) { { old_column: "name", column: "name1", }.merge!(position_hash).merge!(col_def_hash) } it_behaves_like "test optional column", "test column definition", "test position", "test parser parsing a change column" end context 'no change to column name' do let(:alter_table_action) { "change#{column} name1 name1 #{col_def}#{position}" } let(:change_action_hash) { { old_column: "name1", column: "name1", }.merge!(position_hash).merge!(col_def_hash) } it_behaves_like "test optional column", "test column definition", "test position", "test parser parsing a change column" end end context 'modify column' do let(:alter_table_action) { "modify#{column} name1 #{col_def}#{position}" } let(:change_action_hash) { { old_column: "name1", column: "name1", }.merge!(position_hash).merge!(col_def_hash) } it_behaves_like "test optional column", "test column definition", "test position", "test parser parsing a change column" end context 'algorithm and lock option' do ["ALGORITHM", "LOCK"].each do |op| context "#{op}" do let(:action) { op.downcase.to_sym } context "with default #{op}" do let(:alter_table_action) { "#{op}#{equal} DEFAULT" } it_behaves_like "test optional equal", "a parser parsing a nonbreaking query" end context "with any #{op}" do let(:alter_table_action) { "#{op.downcase}#{equal} #{ident}" } it_behaves_like "test optional equal", "test identifier", "a parser parsing a nonbreaking query" end context "without spaces between eq and #{op}" do let(:alter_table_action) { "#{op}=#{ident}" } it_behaves_like "test identifier", "a parser parsing a nonbreaking query" end end end end context 'alter column' do let(:action) { :alter_column } context 'set default' do let(:alter_table_action) { "alter#{column} #{ident} set DEFAULT #{signed_literal}" } it_behaves_like "test optional column", "test field identifier", "test signed literal", "a parser parsing a nonbreaking query" end context 'drop default' do let(:alter_table_action) { "alter#{column} #{ident} DROP default" } it_behaves_like "test optional column", "test field identifier", "a parser parsing a nonbreaking query" end end end end