composite_primary_keys 12.0.0 → 12.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/History.rdoc +44 -3
  3. data/README.rdoc +3 -2
  4. data/lib/composite_primary_keys.rb +57 -57
  5. data/lib/composite_primary_keys/active_model/attribute_assignment.rb +19 -0
  6. data/lib/composite_primary_keys/arel/sqlserver.rb +1 -3
  7. data/lib/composite_primary_keys/associations/through_association.rb +2 -1
  8. data/lib/composite_primary_keys/attribute_methods.rb +2 -2
  9. data/lib/composite_primary_keys/attribute_methods/primary_key.rb +13 -0
  10. data/lib/composite_primary_keys/base.rb +12 -1
  11. data/lib/composite_primary_keys/composite_arrays.rb +49 -14
  12. data/lib/composite_primary_keys/connection_adapters/abstract/database_statements.rb +8 -3
  13. data/lib/composite_primary_keys/connection_adapters/abstract_adapter.rb +1 -1
  14. data/lib/composite_primary_keys/connection_adapters/mysql/database_statements.rb +24 -0
  15. data/lib/composite_primary_keys/connection_adapters/sqlserver/database_statements.rb +33 -12
  16. data/lib/composite_primary_keys/core.rb +1 -1
  17. data/lib/composite_primary_keys/persistence.rb +2 -2
  18. data/lib/composite_primary_keys/relation.rb +100 -25
  19. data/lib/composite_primary_keys/relation/finder_methods.rb +6 -6
  20. data/lib/composite_primary_keys/relation/predicate_builder/association_query_value.rb +1 -1
  21. data/lib/composite_primary_keys/validations/uniqueness.rb +1 -1
  22. data/lib/composite_primary_keys/version.rb +1 -1
  23. data/test/abstract_unit.rb +3 -5
  24. data/test/fixtures/article.rb +4 -0
  25. data/test/fixtures/articles.yml +4 -3
  26. data/test/fixtures/comment.rb +1 -3
  27. data/test/fixtures/comments.yml +10 -9
  28. data/test/fixtures/db_definitions/db2-create-tables.sql +0 -14
  29. data/test/fixtures/db_definitions/db2-drop-tables.sql +1 -3
  30. data/test/fixtures/db_definitions/mysql.sql +7 -44
  31. data/test/fixtures/db_definitions/oracle.drop.sql +3 -9
  32. data/test/fixtures/db_definitions/oracle.sql +12 -48
  33. data/test/fixtures/db_definitions/postgresql.sql +7 -44
  34. data/test/fixtures/db_definitions/sqlite.sql +6 -42
  35. data/test/fixtures/db_definitions/sqlserver.sql +5 -41
  36. data/test/fixtures/department.rb +8 -3
  37. data/test/fixtures/departments.yml +4 -4
  38. data/test/fixtures/readings.yml +2 -2
  39. data/test/fixtures/restaurants_suburbs.yml +1 -1
  40. data/test/fixtures/streets.yml +2 -2
  41. data/test/fixtures/suburbs.yml +2 -2
  42. data/test/fixtures/user.rb +3 -2
  43. data/test/test_associations.rb +30 -23
  44. data/test/test_composite_arrays.rb +14 -0
  45. data/test/test_create.rb +15 -18
  46. data/test/test_delete.rb +3 -3
  47. data/test/test_exists.rb +5 -5
  48. data/test/test_find.rb +22 -2
  49. data/test/test_habtm.rb +2 -2
  50. data/test/test_ids.rb +5 -6
  51. data/test/test_nested_attributes.rb +0 -57
  52. data/test/test_polymorphic.rb +29 -13
  53. data/test/test_preload.rb +4 -3
  54. data/test/test_serialize.rb +2 -2
  55. data/test/test_update.rb +19 -1
  56. metadata +6 -64
  57. data/test/fixtures/hack.rb +0 -5
  58. data/test/fixtures/hacks.yml +0 -3
  59. data/test/fixtures/pk_called_id.rb +0 -5
  60. data/test/fixtures/pk_called_ids.yml +0 -11
  61. data/test/fixtures/reference_code_using_composite_key_alias.rb +0 -8
  62. data/test/fixtures/reference_code_using_simple_key_alias.rb +0 -8
  63. data/test/fixtures/seat.rb +0 -5
  64. data/test/fixtures/seats.yml +0 -9
  65. data/test/fixtures/topic.rb +0 -6
  66. data/test/fixtures/topic_source.rb +0 -7
  67. data/test/mkmf.log +0 -592
  68. data/test/test_aliases.rb +0 -18
  69. data/test/test_enum.rb +0 -21
  70. data/test/test_suite.rb +0 -35
@@ -2,20 +2,41 @@ module ActiveRecord
2
2
  module ConnectionAdapters
3
3
  module SQLServer
4
4
  module DatabaseStatements
5
- def sql_for_insert(sql, pk, id_value, sequence_name, binds)
6
- sql = if pk && self.class.use_output_inserted
7
- # CPK
8
- # quoted_pk = SQLServer::Utils.extract_identifiers(pk).quoted
9
- # sql.insert sql.index(/ (DEFAULT )?VALUES/), " OUTPUT INSERTED.#{quoted_pk}"
10
- quoted_pks = [pk].flatten.map {|pk| "INSERTED.#{SQLServer::Utils.extract_identifiers(pk).quoted}"}
11
- sql.dup.insert sql.index(/ (DEFAULT )?VALUES/), " OUTPUT #{quoted_pks.join(", ")}"
12
- else
13
- "#{sql}; SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident"
5
+ def sql_for_insert(sql, pk, binds)
6
+ if pk.nil?
7
+ table_name = query_requires_identity_insert?(sql)
8
+ pk = primary_key(table_name)
14
9
  end
15
10
 
16
- # CPK
17
- # super
18
- [sql, binds]
11
+ sql = if pk && use_output_inserted? && !database_prefix_remote_server?
12
+ # CPK
13
+ #quoted_pk = SQLServer::Utils.extract_identifiers(pk).quoted
14
+ quoted_pk = Array(pk).map {|subkey| SQLServer::Utils.extract_identifiers(subkey).quoted}
15
+
16
+ table_name ||= get_table_name(sql)
17
+ exclude_output_inserted = exclude_output_inserted_table_name?(table_name, sql)
18
+ if exclude_output_inserted
19
+ id_sql_type = exclude_output_inserted.is_a?(TrueClass) ? "bigint" : exclude_output_inserted
20
+ # CPK
21
+ # <<~SQL.squish
22
+ # DECLARE @ssaIdInsertTable table (#{quoted_pk} #{id_sql_type});
23
+ # #{sql.dup.insert sql.index(/ (DEFAULT )?VALUES/), " OUTPUT INSERTED.#{quoted_pk} INTO @ssaIdInsertTable"}
24
+ # SELECT CAST(#{quoted_pk.join(',')} AS #{id_sql_type}) FROM @ssaIdInsertTable
25
+ # SQL
26
+ <<~SQL.squish
27
+ DECLARE @ssaIdInsertTable table (#{quoted_pk.map {|subkey| "#{subkey} #{id_sql_type}"}.join(", ")});
28
+ #{sql.dup.insert sql.index(/ (DEFAULT )?VALUES/), " OUTPUT INSERTED.#{quoted_pk.join(', INSERTED.')} INTO @ssaIdInsertTable"}
29
+ SELECT #{quoted_pk.map {|subkey| "CAST(#{subkey} AS #{id_sql_type}) #{subkey}"}.join(", ")} FROM @ssaIdInsertTable
30
+ SQL
31
+ else
32
+ # CPK
33
+ # sql.dup.insert sql.index(/ (DEFAULT )?VALUES/), " OUTPUT INSERTED.#{quoted_pk}"
34
+ sql.dup.insert sql.index(/ (DEFAULT )?VALUES/), " OUTPUT INSERTED.#{quoted_pk.join(', INSERTED.')}"
35
+ end
36
+ else
37
+ "#{sql}; SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident"
38
+ end
39
+ super
19
40
  end
20
41
  end
21
42
  end
@@ -40,7 +40,7 @@ module ActiveRecord
40
40
 
41
41
  record = statement.execute([id], connection)&.first
42
42
  unless record
43
- raise RecordNotFound.new("Couldn't find #{name} with '#{key}'=#{id}", name, key, id)
43
+ raise ::ActiveRecord::RecordNotFound.new("Couldn't find #{name} with '#{key}'=#{id}", name, key, id)
44
44
  end
45
45
  record
46
46
  end
@@ -65,8 +65,8 @@ module ActiveRecord
65
65
  )
66
66
 
67
67
  # CPK
68
- if self.composite? && self.id.compact.empty?
69
- self.id = new_id
68
+ if self.composite?
69
+ self.id = self.id.zip(Array(new_id)).map {|id1, id2| id2.nil? ? id1 : id2}
70
70
  else
71
71
  self.id ||= new_id if self.class.primary_key
72
72
  end
@@ -27,15 +27,8 @@ module ActiveRecord
27
27
  stmt = Arel::UpdateManager.new
28
28
  # CPK
29
29
  if @klass.composite?
30
- stmt.table(table)
31
-
32
- arel_attributes = primary_keys.map do |key|
33
- arel_attribute(key)
34
- end.to_composite_keys
35
-
36
- subselect = subquery_for(arel_attributes, arel)
37
-
38
- stmt.wheres = [arel_attributes.in(subselect)]
30
+ stmt.table(arel_table)
31
+ cpk_subquery(stmt)
39
32
  else
40
33
  stmt.table(arel.join_sources.empty? ? table : arel.source)
41
34
  stmt.key = arel_attribute(primary_key)
@@ -75,22 +68,16 @@ module ActiveRecord
75
68
  end
76
69
 
77
70
  stmt = Arel::DeleteManager.new
78
- # CPK
79
- if @klass.composite?
80
- stmt.from(table)
81
-
82
- arel_attributes = primary_keys.map do |key|
83
- arel_attribute(key)
84
- end.to_composite_keys
85
71
 
86
- subselect = subquery_for(arel_attributes, arel)
87
-
88
- stmt.wheres = [arel_attributes.in(subselect)]
72
+ if @klass.composite?
73
+ stmt.from(arel_table)
74
+ cpk_subquery(stmt)
89
75
  else
90
76
  stmt.from(arel.join_sources.empty? ? table : arel.source)
91
77
  stmt.key = arel_attribute(primary_key)
92
78
  stmt.wheres = arel.constraints
93
79
  end
80
+
94
81
  stmt.take(arel.limit)
95
82
  stmt.offset(arel.offset)
96
83
  stmt.order(*arel.orders)
@@ -102,17 +89,105 @@ module ActiveRecord
102
89
  end
103
90
 
104
91
  # CPK
105
- def subquery_for(key, select)
106
- subselect = select.clone
107
- subselect.projections = key
92
+ def cpk_subquery(stmt)
93
+ # For update and delete statements we need a way to specify which records should
94
+ # get updated. By default, Rails creates a nested IN subquery that uses the primary
95
+ # key. Postgresql, Sqlite, MariaDb and Oracle support IN subqueries with multiple
96
+ # columns but MySQL and SqlServer do not. Instead SQL server supports EXISTS queries
97
+ # and MySQL supports obfuscated IN queries. Thus we need to check the type of
98
+ # database adapter to decide how to proceed.
99
+ if defined?(ActiveRecord::ConnectionAdapters::Mysql2Adapter) && connection.is_a?(ActiveRecord::ConnectionAdapters::Mysql2Adapter)
100
+ cpk_mysql_subquery(stmt)
101
+ elsif defined?(ActiveRecord::ConnectionAdapters::SQLServerAdapter) && connection.is_a?(ActiveRecord::ConnectionAdapters::SQLServerAdapter)
102
+ cpk_exists_subquery(stmt)
103
+ else
104
+ cpk_in_subquery(stmt)
105
+ end
106
+ end
107
+
108
+ # Used by postgresql, sqlite, mariadb and oracle. Example query:
109
+ #
110
+ # UPDATE reference_codes
111
+ # SET ...
112
+ # WHERE (reference_codes.reference_type_id, reference_codes.reference_code) IN
113
+ # (SELECT reference_codes.reference_type_id, reference_codes.reference_code
114
+ # FROM reference_codes)
115
+ def cpk_in_subquery(stmt)
116
+ # Setup the subquery
117
+ subquery = arel.clone
118
+ subquery.projections = primary_keys.map do |key|
119
+ arel_table[key]
120
+ end
121
+
122
+ where_fields = primary_keys.map do |key|
123
+ arel_table[key]
124
+ end
125
+ where = Arel::Nodes::Grouping.new(where_fields).in(subquery)
126
+ stmt.wheres = [where]
127
+ end
128
+
129
+ # CPK. This is an alternative to IN subqueries. It is used by sqlserver.
130
+ # Example query:
131
+ #
132
+ # UPDATE reference_codes
133
+ # SET ...
134
+ # WHERE EXISTS
135
+ # (SELECT 1
136
+ # FROM reference_codes cpk_child
137
+ # WHERE reference_codes.reference_type_id = cpk_child.reference_type_id AND
138
+ # reference_codes.reference_code = cpk_child.reference_code)
139
+ def cpk_exists_subquery(stmt)
140
+ arel_attributes = primary_keys.map do |key|
141
+ arel_attribute(key)
142
+ end.to_composite_keys
143
+
144
+ # Clone the query
145
+ subselect = arel.clone
146
+
147
+ # Alias the table - we assume just one table
148
+ aliased_table = subselect.froms.first
149
+ aliased_table.table_alias = "cpk_child"
150
+
151
+ # Project - really we could just set this to "1"
152
+ subselect.projections = arel_attributes
153
+
154
+ # Setup correlation to the outer query via where clauses
155
+ primary_keys.map do |key|
156
+ outer_attribute = arel_table[key]
157
+ inner_attribute = aliased_table[key]
158
+ where = outer_attribute.eq(inner_attribute)
159
+ subselect.where(where)
160
+ end
161
+ stmt.wheres = [Arel::Nodes::Exists.new(subselect)]
162
+ end
163
+
164
+ # CPK. This is the old way CPK created subqueries and is used by MySql.
165
+ # MySQL does not support referencing the same table that is being UPDATEd or
166
+ # DELETEd in a subquery so we obfuscate it. The ugly query looks like this:
167
+ #
168
+ # UPDATE `reference_codes`
169
+ # SET ...
170
+ # WHERE (reference_codes.reference_type_id, reference_codes.reference_code) IN
171
+ # (SELECT reference_type_id,reference_code
172
+ # FROM (SELECT DISTINCT `reference_codes`.`reference_type_id`, `reference_codes`.`reference_code`
173
+ # FROM `reference_codes`) __active_record_temp)
174
+ def cpk_mysql_subquery(stmt)
175
+ arel_attributes = primary_keys.map do |key|
176
+ arel_attribute(key)
177
+ end.to_composite_keys
178
+
179
+ subselect = arel.clone
180
+ subselect.projections = arel_attributes
108
181
 
109
182
  # Materialize subquery by adding distinct
110
183
  # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
111
- subselect.distinct unless select.limit || select.offset || select.orders.any?
184
+ subselect.distinct unless arel.limit || arel.offset || arel.orders.any?
185
+
186
+ key_name = arel_attributes.map(&:name).join(',')
112
187
 
113
- key_name = Array(key).map {|a_key| a_key.name }.join(',')
188
+ manager = Arel::SelectManager.new(subselect.as("__active_record_temp")).project(Arel.sql(key_name))
114
189
 
115
- Arel::SelectManager.new(subselect.as("__active_record_temp")).project(Arel.sql(key_name))
190
+ stmt.wheres = [Arel::Nodes::In.new(arel_attributes, manager.ast)]
116
191
  end
117
192
  end
118
193
  end
@@ -83,7 +83,7 @@ module CompositePrimaryKeys
83
83
 
84
84
  # CPK
85
85
  # expects_array = ids.first.kind_of?(Array)
86
- ids = CompositePrimaryKeys.normalize(ids)
86
+ ids = CompositePrimaryKeys.normalize(ids, @klass.primary_keys.length)
87
87
  expects_array = ids.flatten != ids.flatten(1)
88
88
  return ids.first if expects_array && ids.first.empty?
89
89
 
@@ -96,7 +96,7 @@ module CompositePrimaryKeys
96
96
  case ids.size
97
97
  when 0
98
98
  error_message = "Couldn't find #{model_name} without an ID"
99
- raise RecordNotFound.new(error_message, model_name, primary_key)
99
+ raise ::ActiveRecord::RecordNotFound.new(error_message, model_name, primary_key)
100
100
  when 1
101
101
  result = find_one(ids.first)
102
102
  expects_array ? [ result ] : result
@@ -154,10 +154,10 @@ module CompositePrimaryKeys
154
154
  # CPK
155
155
  if composite?
156
156
  ids = if ids.length == 1
157
- ids.first.split(CompositePrimaryKeys::ID_SEP).to_composite_keys
158
- else
159
- ids.to_composite_keys
160
- end
157
+ CompositePrimaryKeys::CompositeKeys.parse(ids.first)
158
+ else
159
+ ids.to_composite_keys
160
+ end
161
161
  end
162
162
 
163
163
  return find_some_ordered(ids) unless order_values.present?
@@ -6,7 +6,7 @@ module ActiveRecord
6
6
  if associated_table.association_join_foreign_key.is_a?(Array)
7
7
  if ids.is_a?(ActiveRecord::Relation)
8
8
  ids.map do |id|
9
- id.ids_hash
9
+ associated_table.association_join_foreign_key.zip(id.id).to_h
10
10
  end
11
11
  else
12
12
  [associated_table.association_join_foreign_key.zip(ids).to_h]
@@ -24,7 +24,7 @@ module ActiveRecord
24
24
  error_options = options.except(:case_sensitive, :scope, :conditions)
25
25
  error_options[:value] = value
26
26
 
27
- record.errors.add(attribute, :taken, error_options)
27
+ record.errors.add(attribute, :taken, **error_options)
28
28
  end
29
29
  end
30
30
  end
@@ -2,7 +2,7 @@ module CompositePrimaryKeys
2
2
  module VERSION
3
3
  MAJOR = 12
4
4
  MINOR = 0
5
- TINY = 0
5
+ TINY = 5
6
6
  STRING = [MAJOR, MINOR, TINY].join('.')
7
7
  end
8
8
  end
@@ -1,12 +1,10 @@
1
1
  spec_name = ENV['ADAPTER'] || 'sqlite'
2
2
  require 'bundler'
3
- Bundler.require(:default, spec_name.to_sym)
3
+ require 'minitest/autorun'
4
4
 
5
- # To make debugging easier, test within this source tree versus an installed gem
6
- $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
5
+ Bundler.setup(:default, spec_name.to_sym)
6
+ Bundler.require(:default, spec_name.to_sym)
7
7
  require 'composite_primary_keys'
8
- require 'minitest/autorun'
9
- require 'active_support/test_case'
10
8
 
11
9
  # Require the connection spec
12
10
  PROJECT_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
@@ -2,5 +2,9 @@ class Article < ActiveRecord::Base
2
2
  validates :id, uniqueness: true, numericality: true, allow_nil: true, allow_blank: true, on: :create
3
3
  has_many :readings, :dependent => :delete_all
4
4
  has_many :users, :through => :readings
5
+
6
+ has_many :comments, :dependent => :delete_all
7
+ has_many :employee_commentators, :through => :comments, :source => :person, :source_type => :employee
8
+ has_many :user_commentators, :through => :comments, :source => :person, :source_type => "User"
5
9
  end
6
10
 
@@ -1,7 +1,8 @@
1
1
  first:
2
- id: 1
3
2
  name: Article One
4
3
 
5
4
  second:
6
- id: 2
7
- name: Article Two
5
+ name: Article Two
6
+
7
+ third:
8
+ name: Article Three
@@ -1,7 +1,5 @@
1
1
  class Comment < ActiveRecord::Base
2
2
  belongs_to :person, :polymorphic => true
3
- belongs_to :hack
4
-
5
- enum :shown => [ :true, :false ]
3
+ belongs_to :article
6
4
  end
7
5
 
@@ -1,16 +1,17 @@
1
- comment1:
1
+ employee_comment:
2
2
  id: 1
3
+ article: first
3
4
  person_id: 1
4
5
  person_type: Employee
5
-
6
- comment2:
6
+
7
+ user_comment_1:
7
8
  id: 2
9
+ article: second
8
10
  person_id: 1
9
11
  person_type: User
10
- hack_id: 7
11
-
12
- comment3:
12
+
13
+ user_comment_2:
13
14
  id: 3
14
- person_id: 7
15
- person_type: Hack
16
-
15
+ article: second
16
+ person_id: 2
17
+ person_type: User
@@ -1,17 +1,3 @@
1
- CREATE TABLE topics (
2
- id integer NOT NULL,
3
- name varchar(50) default NULL,
4
- feed_size integer default NULL,
5
- PRIMARY KEY (id)
6
- );
7
-
8
- CREATE TABLE topic_sources (
9
- topic_id integer NOT NULL,
10
- platform varchar(50) NOT NULL,
11
- keywords varchar(50) default NULL,
12
- PRIMARY KEY (topic_id,platform)
13
- );
14
-
15
1
  CREATE TABLE reference_types (
16
2
  reference_type_id integer NOT NULL generated by default as identity (start with 100, increment by 1, no cache),
17
3
  type_label varchar(50) default NULL,
@@ -14,6 +14,4 @@ drop table PRODUCT_TARIFFS;
14
14
  drop table KITCHEN_SINK;
15
15
  drop table RESTAURANTS;
16
16
  drop table RESTAURANTS_SUBURBS;
17
- drop table PRODUCTS_RESTAURANTS;
18
- drop table TOPICS;
19
- drop table TOPIC_SOURCES;
17
+ drop table PRODUCTS_RESTAURANTS;
@@ -1,17 +1,3 @@
1
- create table topics (
2
- id int not null auto_increment,
3
- name varchar(50) default null,
4
- feed_size int default null,
5
- primary key (id)
6
- );
7
-
8
- create table topic_sources (
9
- topic_id int not null,
10
- platform varchar(50) not null,
11
- keywords varchar(50) default null,
12
- primary key (topic_id,platform)
13
- );
14
-
15
1
  create table reference_types (
16
2
  reference_type_id int not null auto_increment,
17
3
  type_label varchar(50) default null,
@@ -52,7 +38,7 @@ create table product_tariffs (
52
38
  );
53
39
 
54
40
  create table suburbs (
55
- city_id int not null,
41
+ city_id int not null auto_increment,
56
42
  suburb_id int not null,
57
43
  name varchar(50) not null,
58
44
  primary key (city_id, suburb_id)
@@ -86,7 +72,7 @@ create table readings (
86
72
  primary key (id)
87
73
  );
88
74
 
89
- create table groups (
75
+ create table `groups` (
90
76
  id int not null auto_increment,
91
77
  name varchar(50) not null,
92
78
  primary key (id)
@@ -107,9 +93,9 @@ create table membership_statuses (
107
93
  );
108
94
 
109
95
  create table departments (
110
- department_id int not null,
96
+ id int not null auto_increment,
111
97
  location_id int not null,
112
- primary key (department_id, location_id)
98
+ primary key (id, location_id)
113
99
  );
114
100
 
115
101
  create table employees (
@@ -122,16 +108,9 @@ create table employees (
122
108
 
123
109
  create table comments (
124
110
  id int not null auto_increment,
125
- person_id int default null,
126
- shown int default null,
127
- person_type varchar(100) default null,
128
- hack_id int default null,
129
- primary key (id)
130
- );
131
-
132
- create table hacks (
133
- id int not null auto_increment,
134
- name varchar(50) not null,
111
+ article_id int not null,
112
+ person_id int not null,
113
+ person_type varchar(100) not null,
135
114
  primary key (id)
136
115
  );
137
116
 
@@ -184,13 +163,6 @@ create table room_assignments (
184
163
  room_id int not null
185
164
  );
186
165
 
187
- create table seats (
188
- flight_number int not null,
189
- seat int not null,
190
- customer int,
191
- primary key (flight_number, seat)
192
- );
193
-
194
166
  create table capitols (
195
167
  country varchar(100) not null,
196
168
  city varchar(100) not null,
@@ -206,13 +178,4 @@ create table products_restaurants (
206
178
  create table employees_groups (
207
179
  employee_id int not null,
208
180
  group_id int not null
209
- );
210
-
211
- create table pk_called_ids (
212
- id integer not null,
213
- reference_code int not null,
214
- code_label varchar(50) default null,
215
- abbreviation varchar(50) default null,
216
- description varchar(50) default null,
217
- primary key (id, reference_code)
218
181
  );