activerecord-sqlserver-adapter 6.0.2 → 6.1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -56
  3. data/README.md +28 -11
  4. data/VERSION +1 -1
  5. data/activerecord-sqlserver-adapter.gemspec +1 -1
  6. data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +2 -0
  7. data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +5 -10
  8. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +9 -2
  9. data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +2 -0
  10. data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +2 -0
  11. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +0 -4
  12. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +27 -15
  13. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +4 -3
  14. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +22 -1
  15. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +9 -3
  16. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +8 -6
  17. data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +36 -7
  18. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +0 -1
  19. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +2 -2
  20. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +2 -1
  21. data/lib/active_record/connection_adapters/sqlserver/utils.rb +1 -1
  22. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +100 -70
  23. data/lib/active_record/connection_adapters/sqlserver_column.rb +75 -19
  24. data/lib/active_record/sqlserver_base.rb +9 -15
  25. data/lib/active_record/tasks/sqlserver_database_tasks.rb +17 -14
  26. data/lib/arel/visitors/sqlserver.rb +74 -29
  27. data/test/cases/adapter_test_sqlserver.rb +27 -17
  28. data/test/cases/change_column_collation_test_sqlserver.rb +33 -0
  29. data/test/cases/coerced_tests.rb +544 -77
  30. data/test/cases/column_test_sqlserver.rb +4 -0
  31. data/test/cases/disconnected_test_sqlserver.rb +39 -0
  32. data/test/cases/execute_procedure_test_sqlserver.rb +9 -0
  33. data/test/cases/fetch_test_sqlserver.rb +18 -0
  34. data/test/cases/in_clause_test_sqlserver.rb +27 -0
  35. data/test/cases/migration_test_sqlserver.rb +7 -0
  36. data/test/cases/order_test_sqlserver.rb +7 -0
  37. data/test/cases/primary_keys_test_sqlserver.rb +103 -0
  38. data/test/cases/rake_test_sqlserver.rb +38 -2
  39. data/test/cases/schema_dumper_test_sqlserver.rb +9 -0
  40. data/test/migrations/create_clients_and_change_column_collation.rb +19 -0
  41. data/test/models/sqlserver/composite_pk.rb +9 -0
  42. data/test/models/sqlserver/sst_string_collation.rb +3 -0
  43. data/test/schema/sqlserver_specific_schema.rb +25 -0
  44. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic.dump +0 -0
  45. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic_associations.dump +0 -0
  46. data/test/support/sql_counter_sqlserver.rb +14 -12
  47. metadata +23 -8
  48. data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +0 -28
@@ -299,6 +299,8 @@ class ColumnTestSQLServer < ActiveRecord::TestCase
299
299
  _(obj.date).must_equal Date.civil(0001, 4, 1)
300
300
  obj.reload
301
301
  _(obj.date).must_equal Date.civil(0001, 4, 1)
302
+ # Can filter by date range
303
+ _(obj).must_equal obj.class.where(date: obj.date..Date::Infinity.new).first
302
304
  # Can keep and return assigned date.
303
305
  assert_obj_set_and_save :date, Date.civil(1972, 04, 14)
304
306
  # Can accept and cast time objects.
@@ -333,6 +335,8 @@ class ColumnTestSQLServer < ActiveRecord::TestCase
333
335
  obj.reload
334
336
  _(obj.datetime).must_equal time, "Microseconds were <#{obj.datetime.usec}> vs <3000>"
335
337
  _(obj).must_equal obj.class.where(datetime: time).first
338
+ # Can filter by datetime range
339
+ _(obj).must_equal obj.class.where(datetime: time..DateTime::Infinity.new).first
336
340
  # Will cast to true DB value on attribute write, save and return again.
337
341
  time = Time.utc 2010, 04, 01, 12, 34, 56, 234567
338
342
  time2 = Time.utc 2010, 04, 01, 12, 34, 56, 233000
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cases/helper_sqlserver"
4
+
5
+ class TestDisconnectedAdapter < ActiveRecord::TestCase
6
+ self.use_transactional_tests = false
7
+
8
+ def setup
9
+ @connection = ActiveRecord::Base.connection
10
+ end
11
+
12
+ teardown do
13
+ return if in_memory_db?
14
+ db_config = ActiveRecord::Base.connection_db_config
15
+ ActiveRecord::Base.establish_connection(db_config)
16
+ end
17
+
18
+ test "can't execute procedures while disconnected" do
19
+ @connection.execute_procedure :sp_tables, "sst_datatypes"
20
+ @connection.disconnect!
21
+ assert_raises(ActiveRecord::ConnectionNotEstablished, 'SQL Server client is not connected') do
22
+ @connection.execute_procedure :sp_tables, "sst_datatypes"
23
+ end
24
+ end
25
+
26
+ test "can't execute query while disconnected" do
27
+ sql = "SELECT count(*) from products WHERE id IN(@0, @1)"
28
+ binds = [
29
+ ActiveRecord::Relation::QueryAttribute.new("id", 2, ActiveRecord::Type::BigInteger.new),
30
+ ActiveRecord::Relation::QueryAttribute.new("id", 2, ActiveRecord::Type::BigInteger.new)
31
+ ]
32
+
33
+ @connection.exec_query sql, "TEST", binds
34
+ @connection.disconnect!
35
+ assert_raises(ActiveRecord::ConnectionNotEstablished, 'SQL Server client is not connected') do
36
+ @connection.exec_query sql, "TEST", binds
37
+ end
38
+ end
39
+ end
@@ -41,4 +41,13 @@ class ExecuteProcedureTestSQLServer < ActiveRecord::TestCase
41
41
  date_base = connection.select_value("select GETUTCDATE()")
42
42
  assert_equal date_base.change(usec: 0), date_proc.change(usec: 0)
43
43
  end
44
+
45
+ it 'test deprecation with transaction return when executing procedure' do
46
+ assert_deprecated do
47
+ ActiveRecord::Base.transaction do
48
+ connection.execute_procedure("my_getutcdate")
49
+ return
50
+ end
51
+ end
52
+ end
44
53
  end
@@ -49,3 +49,21 @@ class FetchTestSqlserver < ActiveRecord::TestCase
49
49
  @books = (1..10).map { |i| Book.create! name: "Name-#{i}" }
50
50
  end
51
51
  end
52
+
53
+ class DeterministicFetchWithCompositePkTestSQLServer < ActiveRecord::TestCase
54
+ it "orders by the identity column if table has one" do
55
+ SSCompositePkWithIdentity.delete_all
56
+ SSCompositePkWithIdentity.create(pk_col_two: 2)
57
+ SSCompositePkWithIdentity.create(pk_col_two: 1)
58
+
59
+ _(SSCompositePkWithIdentity.take(1).map(&:pk_col_two)).must_equal [2]
60
+ end
61
+
62
+ it "orders by the first column if table has no identity column" do
63
+ SSCompositePkWithoutIdentity.delete_all
64
+ SSCompositePkWithoutIdentity.create(pk_col_one: 2, pk_col_two: 2)
65
+ SSCompositePkWithoutIdentity.create(pk_col_one: 1, pk_col_two: 1)
66
+
67
+ _(SSCompositePkWithoutIdentity.take(1).map(&:pk_col_two)).must_equal [1]
68
+ end
69
+ end
@@ -33,4 +33,31 @@ class InClauseTestSQLServer < ActiveRecord::TestCase
33
33
  assert_includes posts.to_sql, "ORDER BY [authors].[name]"
34
34
  assert_equal 8, posts.length
35
35
  end
36
+
37
+ it "removes ordering from 'not' subqueries" do
38
+ authors_subquery = Author.where.not(name: ["Mary", "Bob"]).order(:name)
39
+ posts = Post.where(author: authors_subquery)
40
+
41
+ assert_includes authors_subquery.to_sql, "ORDER BY [authors].[name]"
42
+ assert_not_includes posts.to_sql, "ORDER BY [authors].[name]"
43
+ assert_equal 5, posts.length
44
+ end
45
+
46
+ it "does not remove ordering from 'not' subquery that includes a limit" do
47
+ authors_subquery = Author.where.not(name: ["Ronan", "Mary", "Bob"]).order(:name).limit(2)
48
+ posts = Post.where(author: authors_subquery)
49
+
50
+ assert_includes authors_subquery.to_sql, "ORDER BY [authors].[name]"
51
+ assert_includes posts.to_sql, "ORDER BY [authors].[name]"
52
+ assert_equal 5, posts.length
53
+ end
54
+
55
+ it "does not remove ordering from 'not' subquery that includes an offset" do
56
+ authors_subquery = Author.where.not(name: ["David", "Ronan", "Cian"]).order(:name).offset(1)
57
+ posts = Post.where(author: authors_subquery)
58
+
59
+ assert_includes authors_subquery.to_sql, "ORDER BY [authors].[name]"
60
+ assert_includes posts.to_sql, "ORDER BY [authors].[name]"
61
+ assert_equal 3, posts.length
62
+ end
36
63
  end
@@ -63,6 +63,13 @@ class MigrationTestSQLServer < ActiveRecord::TestCase
63
63
  it "change null and default" do
64
64
  assert_nothing_raised { connection.change_column :people, :first_name, :text, null: true, default: nil }
65
65
  end
66
+
67
+ it "change collation" do
68
+ assert_nothing_raised { connection.change_column :sst_string_collation, :string_with_collation, :varchar, collation: :SQL_Latin1_General_CP437_BIN }
69
+
70
+ SstStringCollation.reset_column_information
71
+ assert_equal "SQL_Latin1_General_CP437_BIN", SstStringCollation.columns_hash['string_with_collation'].collation
72
+ end
66
73
  end
67
74
 
68
75
  describe "#create_schema" do
@@ -143,4 +143,11 @@ class OrderTestSQLServer < ActiveRecord::TestCase
143
143
  assert_equal post1, Post.order(Arel.sql(order)).first
144
144
  assert_equal post2, Post.order(Arel.sql(order)).second
145
145
  end
146
+
147
+ # Executing this kind of queries will raise "A column has been specified more than once in the order by list"
148
+ # This test shows that we don't do anything to prevent this
149
+ it "doesn't deduplicate semantically equal orders" do
150
+ sql = Post.order(:id).order("posts.id ASC").to_sql
151
+ assert_equal "SELECT [posts].* FROM [posts] ORDER BY [posts].[id] ASC, posts.id ASC", sql
152
+ end
146
153
  end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cases/helper_sqlserver"
4
+ require "support/schema_dumping_helper"
5
+
6
+ class PrimaryKeyUuidTypeTest < ActiveRecord::TestCase
7
+ include SchemaDumpingHelper
8
+
9
+ self.use_transactional_tests = false
10
+
11
+ class Barcode < ActiveRecord::Base
12
+ end
13
+
14
+ setup do
15
+ @connection = ActiveRecord::Base.connection
16
+ @connection.create_table(:barcodes, primary_key: "code", id: :uuid, force: true)
17
+ end
18
+
19
+ teardown do
20
+ @connection.drop_table(:barcodes, if_exists: true)
21
+ end
22
+
23
+ def test_any_type_primary_key
24
+ assert_equal "code", Barcode.primary_key
25
+
26
+ column = Barcode.column_for_attribute(Barcode.primary_key)
27
+ assert_not column.null
28
+ assert_equal :uuid, column.type
29
+ assert_not_predicate column, :is_identity?
30
+ assert_predicate column, :is_primary?
31
+ ensure
32
+ Barcode.reset_column_information
33
+ end
34
+
35
+ test "schema dump primary key includes default" do
36
+ schema = dump_table_schema "barcodes"
37
+ assert_match %r/create_table "barcodes", primary_key: "code", id: :uuid, default: -> { "newid\(\)" }/, schema
38
+ end
39
+ end
40
+
41
+ class PrimaryKeyIntegerTest < ActiveRecord::TestCase
42
+ include SchemaDumpingHelper
43
+
44
+ self.use_transactional_tests = false
45
+
46
+ class Barcode < ActiveRecord::Base
47
+ end
48
+
49
+ class Widget < ActiveRecord::Base
50
+ end
51
+
52
+ setup do
53
+ @connection = ActiveRecord::Base.connection
54
+ end
55
+
56
+ teardown do
57
+ @connection.drop_table :barcodes, if_exists: true
58
+ @connection.drop_table :widgets, if_exists: true
59
+ end
60
+
61
+ test "integer primary key without default" do
62
+ @connection.create_table(:widgets, id: :integer, force: true)
63
+ column = @connection.columns(:widgets).find { |c| c.name == "id" }
64
+ assert_predicate column, :is_primary?
65
+ assert_predicate column, :is_identity?
66
+ assert_equal :integer, column.type
67
+ assert_not_predicate column, :bigint?
68
+
69
+ schema = dump_table_schema "widgets"
70
+ assert_match %r/create_table "widgets", id: :integer, force: :cascade do/, schema
71
+ end
72
+
73
+ test "bigint primary key without default" do
74
+ @connection.create_table(:widgets, id: :bigint, force: true)
75
+ column = @connection.columns(:widgets).find { |c| c.name == "id" }
76
+ assert_predicate column, :is_primary?
77
+ assert_predicate column, :is_identity?
78
+ assert_equal :integer, column.type
79
+ assert_predicate column, :bigint?
80
+
81
+ schema = dump_table_schema "widgets"
82
+ assert_match %r/create_table "widgets", force: :cascade do/, schema
83
+ end
84
+
85
+ test "don't set identity to integer and bigint when there is a default" do
86
+ @connection.create_table(:barcodes, id: :integer, default: nil, force: true)
87
+ @connection.create_table(:widgets, id: :bigint, default: nil, force: true)
88
+
89
+ column = @connection.columns(:widgets).find { |c| c.name == "id" }
90
+ assert_predicate column, :is_primary?
91
+ assert_not_predicate column, :is_identity?
92
+
93
+ schema = dump_table_schema "widgets"
94
+ assert_match %r/create_table "widgets", id: :bigint, default: nil, force: :cascade do/, schema
95
+
96
+ column = @connection.columns(:barcodes).find { |c| c.name == "id" }
97
+ assert_predicate column, :is_primary?
98
+ assert_not_predicate column, :is_identity?
99
+
100
+ schema = dump_table_schema "barcodes"
101
+ assert_match %r/create_table "barcodes", id: :integer, default: nil, force: :cascade do/, schema
102
+ end
103
+ end
@@ -10,8 +10,9 @@ class SQLServerRakeTest < ActiveRecord::TestCase
10
10
 
11
11
  let(:db_tasks) { ActiveRecord::Tasks::DatabaseTasks }
12
12
  let(:new_database) { "activerecord_unittest_tasks" }
13
- let(:default_configuration) { ARTest.connection_config["arunit"] }
13
+ let(:default_configuration) { ARTest.test_configuration_hashes["arunit"] }
14
14
  let(:configuration) { default_configuration.merge("database" => new_database) }
15
+ let(:db_config) { ActiveRecord::Base.configurations.resolve(configuration) }
15
16
 
16
17
  before { skip "on azure" if azure_skip }
17
18
  before { disconnect! unless azure_skip }
@@ -151,7 +152,42 @@ class SQLServerRakeStructureDumpLoadTest < SQLServerRakeTest
151
152
  _(filedata).must_match %r{CREATE TABLE dbo\.users}
152
153
  db_tasks.purge(configuration)
153
154
  _(connection.tables).wont_include "users"
154
- db_tasks.load_schema configuration, :sql, filename
155
+ db_tasks.load_schema db_config, :sql, filename
155
156
  _(connection.tables).must_include "users"
156
157
  end
157
158
  end
159
+
160
+ class SQLServerRakeSchemaCacheDumpLoadTest < SQLServerRakeTest
161
+ let(:filename) { File.join ARTest::SQLServer.test_root_sqlserver, "schema_cache.yml" }
162
+ let(:filedata) { File.read(filename) }
163
+
164
+ before do
165
+ quietly { db_tasks.create(configuration) }
166
+
167
+ connection.create_table :users, force: true do |t|
168
+ t.string :name, null: false
169
+ end
170
+ end
171
+
172
+ after do
173
+ FileUtils.rm_rf(filename)
174
+ end
175
+
176
+ it "dumps schema cache with SQL Server metadata" do
177
+ quietly { db_tasks.dump_schema_cache connection, filename }
178
+
179
+ schema_cache = YAML.load(File.read(filename))
180
+
181
+ col_id, col_name = connection.schema_cache.columns("users")
182
+
183
+ assert col_id.is_identity
184
+ assert col_id.is_primary
185
+ assert_equal col_id.ordinal_position, 1
186
+ assert_equal col_id.table_name, "users"
187
+
188
+ assert_not col_name.is_identity
189
+ assert_not col_name.is_primary
190
+ assert_equal col_name.ordinal_position, 2
191
+ assert_equal col_name.table_name, "users"
192
+ end
193
+ end
@@ -119,6 +119,15 @@ class SchemaDumperTestSQLServer < ActiveRecord::TestCase
119
119
  assert_line :json_col, type: "text", limit: nil, precision: nil, scale: nil, default: nil
120
120
  end
121
121
 
122
+ it "dump column collation" do
123
+ generate_schema_for_table('sst_string_collation')
124
+
125
+ assert_line :string_without_collation, type: "string", limit: nil, default: nil, collation: nil
126
+ assert_line :string_default_collation, type: "varchar", limit: nil, default: nil, collation: nil
127
+ assert_line :string_with_collation, type: "varchar", limit: nil, default: nil, collation: "SQL_Latin1_General_CP1_CS_AS"
128
+ assert_line :varchar_with_collation, type: "varchar", limit: nil, default: nil, collation: "SQL_Latin1_General_CP1_CS_AS"
129
+ end
130
+
122
131
  # Special Cases
123
132
 
124
133
  it "honor nonstandard primary keys" do
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateClientsAndChangeColumnCollation < ActiveRecord::Migration[5.2]
4
+ def up
5
+ create_table :clients do |t|
6
+ t.string :name
7
+ t.string :code, collation: :SQL_Latin1_General_CP1_CS_AS
8
+
9
+ t.timestamps
10
+ end
11
+
12
+ change_column :clients, :name, :string, collation: 'SQL_Latin1_General_CP1_CS_AS'
13
+ change_column :clients, :code, :string, collation: 'SQL_Latin1_General_CP1_CI_AS'
14
+ end
15
+
16
+ def down
17
+ drop_table :clients
18
+ end
19
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SSCompositePkWithoutIdentity < ActiveRecord::Base
4
+ self.table_name = :sst_composite_without_identity
5
+ end
6
+
7
+ class SSCompositePkWithIdentity < ActiveRecord::Base
8
+ self.table_name = :sst_composite_with_identity
9
+ end
@@ -0,0 +1,3 @@
1
+ class SstStringCollation < ActiveRecord::Base
2
+ self.table_name = "sst_string_collation"
3
+ end
@@ -95,6 +95,13 @@ ActiveRecord::Schema.define do
95
95
  t.column :string_with_multiline_default, :string, default: "Some long default with a\nnew line."
96
96
  end
97
97
 
98
+ create_table :sst_string_collation, collation: :SQL_Latin1_General_CP1_CI_AS, force: true do |t|
99
+ t.string :string_without_collation
100
+ t.varchar :string_default_collation, collation: :SQL_Latin1_General_CP1_CI_AS
101
+ t.varchar :string_with_collation, collation: :SQL_Latin1_General_CP1_CS_AS
102
+ t.varchar :varchar_with_collation, collation: :SQL_Latin1_General_CP1_CS_AS
103
+ end
104
+
98
105
  create_table :sst_edge_schemas, force: true do |t|
99
106
  t.string :description
100
107
  t.column "crazy]]quote", :string
@@ -287,4 +294,22 @@ ActiveRecord::Schema.define do
287
294
  CONSTRAINT PK_UNIQUE_KEY PRIMARY KEY (id)
288
295
  );
289
296
  SQLSERVERUNIQUEKEYS
297
+
298
+ execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sst_composite_without_identity') DROP TABLE sst_composite_without_identity"
299
+ execute <<-COMPOSITE_WITHOUT_IDENTITY
300
+ CREATE TABLE sst_composite_without_identity (
301
+ pk_col_one int NOT NULL,
302
+ pk_col_two int NOT NULL,
303
+ CONSTRAINT PK_sst_composite_without_identity PRIMARY KEY (pk_col_one, pk_col_two)
304
+ );
305
+ COMPOSITE_WITHOUT_IDENTITY
306
+
307
+ execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sst_composite_with_identity') DROP TABLE sst_composite_with_identity"
308
+ execute <<-COMPOSITE_WITH_IDENTITY
309
+ CREATE TABLE sst_composite_with_identity (
310
+ pk_col_one int IDENTITY NOT NULL,
311
+ pk_col_two int NOT NULL,
312
+ CONSTRAINT PK_sst_composite_with_identity PRIMARY KEY (pk_col_one, pk_col_two)
313
+ );
314
+ COMPOSITE_WITH_IDENTITY
290
315
  end
@@ -11,17 +11,19 @@ module ARTest
11
11
  end
12
12
  end
13
13
 
14
- ignored_sql = [
15
- /INFORMATION_SCHEMA\.(TABLES|VIEWS|COLUMNS|KEY_COLUMN_USAGE)/im,
16
- /sys.columns/i,
17
- /SELECT @@version/,
18
- /SELECT @@TRANCOUNT/,
19
- /(BEGIN|COMMIT|ROLLBACK|SAVE) TRANSACTION/,
20
- /SELECT CAST\(.* AS .*\) AS value/,
21
- /SELECT DATABASEPROPERTYEX/im
22
- ]
23
-
24
- sqlcounter = ObjectSpace.each_object(ActiveRecord::SQLCounter).to_a.first
25
- sqlcounter.instance_variable_set :@ignore, Regexp.union(ignored_sql.push(sqlcounter.ignore))
14
+ # TODO: Delete the code below after all Rails 6.1 tests passing.
15
+ #
16
+ # ignored_sql = [
17
+ # /INFORMATION_SCHEMA\.(TABLES|VIEWS|COLUMNS|KEY_COLUMN_USAGE)/im,
18
+ # /sys.columns/i,
19
+ # /SELECT @@version/,
20
+ # /SELECT @@TRANCOUNT/,
21
+ # /(BEGIN|COMMIT|ROLLBACK|SAVE) TRANSACTION/,
22
+ # /SELECT CAST\(.* AS .*\) AS value/,
23
+ # /SELECT DATABASEPROPERTYEX/im
24
+ # ]
25
+ #
26
+ # sqlcounter = ObjectSpace.each_object(ActiveRecord::SQLCounter).to_a.first
27
+ # sqlcounter.instance_variable_set :@ignore, Regexp.union(ignored_sql.push(sqlcounter.ignore))
26
28
  end
27
29
  end