activerecord-tidb-adapter 0.3.0 → 0.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 442a2dcd175987185739a1fad7a8ef8788d0b13b7263018795ac144a4c10ee03
4
- data.tar.gz: e568154643ebebfc8a9dcfd506b18732d52dea1a19b6500b76e9103965fd2cff
3
+ metadata.gz: 85ed982e6735cfcb567e2d863975ba8f56e98dd6243578394e2ca01dc07ca91a
4
+ data.tar.gz: c7aefb6293698e59ce8ff6812b156070622c6ed7c58689e39c4eca5946309959
5
5
  SHA512:
6
- metadata.gz: d01699afa9a6c147317b8b6fd5763bcbf63852bccaa4fe6dd5bac076405bfc02ff98ae177cd1eef39a8aac95493a4e7eac31896260bceb8b78d2c2b3976605c6
7
- data.tar.gz: 932f70292fde728c25d21bf7180dbad488ca4b5d7630bfe669b70dbf1dee0165f24b311d284ed0ab8edd1b5ab1b688d28d29e7a1748c8a571101950568373a00
6
+ metadata.gz: af4ea51bd264d25cd621e3c95438fd9fd3c7df8b2dea533172cbeb0e07110e661dc68b0d294c23a1420e2b9c0a74334b14c46213d59425764a37336702d17034
7
+ data.tar.gz: d07ec9fa2f944d6832c7f70287d4583073ea633b7c8364936801e555ac5592a4c5d21b18e9fff5b12a2599b4b8bfc637c5163cbb0b6604e0af1c5b388bc7f70f
data/Gemfile.lock CHANGED
@@ -87,7 +87,7 @@ GIT
87
87
  PATH
88
88
  remote: .
89
89
  specs:
90
- activerecord-tidb-adapter (0.3.0)
90
+ activerecord-tidb-adapter (0.4.0)
91
91
  activerecord (~> 6.1)
92
92
  mysql2
93
93
 
data/README.md CHANGED
@@ -44,6 +44,62 @@ development:
44
44
 
45
45
  * demo repo with rails 6.1.4: https://github.com/hooopo/activerecord-tidb-adapter-demo
46
46
 
47
+ ## TiDB features
48
+
49
+ **[Sequence](https://docs.pingcap.com/tidb/stable/sql-statement-create-sequence)**
50
+
51
+ Sequence as primary key
52
+
53
+ ```ruby
54
+ class TestSeq < ActiveRecord::Migration[6.1]
55
+ def up
56
+ # more options: increment, min_value, cycle, cache
57
+ create_sequence :orders_seq, start: 1024
58
+ create_table :orders, id: false do |t|
59
+ t.primary_key :id, default: -> { "nextval(orders_seq)" }
60
+ t.string :name
61
+ end
62
+ end
63
+
64
+ def down
65
+ drop_table :orders
66
+ drop_sequence :orders_seq
67
+ end
68
+ end
69
+ ```
70
+
71
+ Generated DDL
72
+ ```sql
73
+ mysql> show create table orders_seq\G;
74
+ *************************** 1. row ***************************
75
+ Sequence: orders_seq
76
+ Create Sequence: CREATE SEQUENCE `orders_seq` start with 1024 minvalue 1 maxvalue 9223372036854775806 increment by 1 nocache nocycle ENGINE=InnoDB
77
+
78
+ mysql> show create table orders\G;
79
+ *************************** 1. row ***************************
80
+ Table: orders
81
+ Create Table: CREATE TABLE `orders` (
82
+ `id` bigint(20) NOT NULL DEFAULT nextval(`activerecord_tidb_adapter_demo_development`.`orders_seq`),
83
+ `name` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
84
+ PRIMARY KEY (`id`) /*T![clustered_index] CLUSTERED */
85
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
86
+
87
+ ```
88
+
89
+ This gem also adds a few helpers to interact with `SEQUENCE`
90
+
91
+ ```ruby
92
+ # Advance sequence and return new value
93
+ ActiveRecord::Base.nextval("numbers")
94
+
95
+ # Return value most recently obtained with nextval for specified sequence.
96
+ ActiveRecord::Base.lastval("numbers")
97
+
98
+ # Set sequence's current value
99
+ ActiveRecord::Base.setval("numbers", 1234)
100
+ ```
101
+
102
+
47
103
  ## Setting up local TiDB server
48
104
 
49
105
  Install [tiup](https://github.com/pingcap/tiup)
@@ -93,7 +149,7 @@ MYSQL_USER=root MYSQL_HOST=127.0.0.1 MYSQL_PORT=4000 tidb=1 ARCONN=tidb bundle e
93
149
 
94
150
  ## Contributing
95
151
 
96
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/activerecord-tidb-adapter.
152
+ Bug reports and pull requests are welcome on GitHub at https://github.com/pingcap/activerecord-tidb-adapter.
97
153
 
98
154
  ## License
99
155
 
data/Rakefile CHANGED
@@ -15,7 +15,7 @@ namespace :test do
15
15
  Rake::TestTask.new('tidb') do |t|
16
16
  t.libs = ARTest::TiDB.test_load_paths
17
17
  t.test_files = test_files
18
- t.warning = !!ENV['WARNING']
18
+ t.warning = !ENV['WARNING'].nil?
19
19
  t.verbose = false
20
20
  end
21
21
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module Type
3
5
  class << self
@@ -4,6 +4,7 @@ require 'active_record/connection_adapters'
4
4
  require 'active_record/connection_adapters/mysql2_adapter'
5
5
  require 'active_record/connection_adapters/tidb/setup'
6
6
  require_relative '../../version'
7
+ require_relative '../sequence'
7
8
 
8
9
  ActiveRecord::ConnectionAdapters::Tidb.initial_setup
9
10
 
@@ -31,6 +32,7 @@ module ActiveRecord
31
32
 
32
33
  module ConnectionAdapters
33
34
  class TidbAdapter < Mysql2Adapter
35
+ include ActiveRecord::Sequence::Adapter
34
36
  ADAPTER_NAME = 'Tidb'
35
37
 
36
38
  def supports_savepoints?
@@ -93,10 +95,38 @@ module ActiveRecord
93
95
  end
94
96
 
95
97
  def self.database_exists?(config)
96
- !!ActiveRecord::Base.tidb_connection(config)
98
+ !ActiveRecord::Base.tidb_connection(config).nil?
97
99
  rescue ActiveRecord::NoDatabaseError
98
100
  false
99
101
  end
102
+
103
+ def new_column_from_field(_table_name, field)
104
+ type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
105
+ default = field[:Default]
106
+ default_function = nil
107
+
108
+ if type_metadata.type == :datetime && /\ACURRENT_TIMESTAMP(?:\([0-6]?\))?\z/i.match?(default)
109
+ default_function = default
110
+ default = nil
111
+ elsif type_metadata.extra == 'DEFAULT_GENERATED'
112
+ default = +"(#{default})" unless default.start_with?('(')
113
+ default_function = default
114
+ default = nil
115
+ elsif default.to_s =~ /nextval/i
116
+ default_function = default
117
+ default = nil
118
+ end
119
+
120
+ MySQL::Column.new(
121
+ field[:Field],
122
+ default,
123
+ type_metadata,
124
+ field[:Null] == 'YES',
125
+ default_function,
126
+ collation: field[:Collation],
127
+ comment: field[:Comment].presence
128
+ )
129
+ end
100
130
  end
101
131
  end
102
132
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/all'
4
+ require 'active_record'
5
+ require 'active_record/connection_adapters/mysql/schema_dumper'
6
+ require 'active_record/migration/command_recorder'
7
+ require 'active_record/schema_dumper'
8
+
9
+ module ActiveRecord
10
+ module Sequence
11
+ require 'active_record/sequence/command_recorder'
12
+ require 'active_record/sequence/adapter'
13
+ require 'active_record/sequence/schema_dumper'
14
+ require 'active_record/sequence/model_methods'
15
+ end
16
+ end
17
+
18
+ ActiveRecord::Migration::CommandRecorder.include(ActiveRecord::Sequence::CommandRecorder)
19
+
20
+ ActiveRecord::SchemaDumper.prepend(ActiveRecord::Sequence::SchemaDumper)
21
+ ActiveRecord::Base.extend(ActiveRecord::Sequence::ModelMethods)
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Sequence
5
+ module Adapter
6
+ def check_sequences
7
+ select_all(
8
+ 'SELECT * FROM information_schema.sequences ORDER BY sequence_name'
9
+ ).to_a
10
+ end
11
+
12
+ def create_sequence(name, options = {})
13
+ increment = options[:increment] || options[:step]
14
+ name = quote_column_name(name)
15
+
16
+ sql = ["CREATE SEQUENCE IF NOT EXISTS #{name}"]
17
+ sql << "INCREMENT BY #{increment}" if increment
18
+ sql << "START WITH #{options[:start]}" if options[:start]
19
+ sql << if options[:cache]
20
+ "CACHE #{options[:cache]}"
21
+ else
22
+ 'NOCACHE'
23
+ end
24
+
25
+ sql << if options[:cycle]
26
+ 'CYCLE'
27
+ else
28
+ 'NOCYCLE'
29
+ end
30
+
31
+ sql << "MIN_VALUE #{options[:min_value]}" if options[:min_value]
32
+
33
+ sql << "COMMENT #{quote(options[:comment].to_s)}" if options[:comment]
34
+
35
+ execute(sql.join("\n"))
36
+ end
37
+
38
+ def drop_sequence(name)
39
+ name = quote_column_name(name)
40
+ sql = "DROP SEQUENCE #{name}"
41
+ execute(sql)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Sequence
5
+ module CommandRecorder
6
+ def create_sequence(name, options = {})
7
+ record(__method__, [name, options])
8
+ end
9
+
10
+ def drop_sequence(name)
11
+ record(__method__, [name])
12
+ end
13
+
14
+ def invert_create_sequence(args)
15
+ name, = args
16
+ [:drop_sequence, [name]]
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Sequence
5
+ module ModelMethods
6
+ def nextval(name)
7
+ name = connection.quote_column_name(name)
8
+ connection.query_value("SELECT nextval(#{name})")
9
+ end
10
+
11
+ def lastval(name)
12
+ name = connection.quote_column_name(name)
13
+ connection.query_value("SELECT lastval(#{name})")
14
+ end
15
+
16
+ def setval(name, value)
17
+ name = connection.quote_column_name(name)
18
+ connection.query_value("SELECT setval(#{name}, #{value})")
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Sequence
5
+ module SchemaDumper
6
+ def header(stream)
7
+ super
8
+ sequences(stream)
9
+ end
10
+
11
+ def sequences(stream)
12
+ sequences = @connection.check_sequences
13
+ return if sequences.empty?
14
+
15
+ sequences.each do |seq|
16
+ start_value = seq['START']
17
+ increment = seq['INCREMENT']
18
+ cache = seq['CACHE']
19
+ cache_value = seq['CACHE_VALUE']
20
+ min_value = seq['MIN_VALUE']
21
+ cycle = seq['CYCLE']
22
+ comment = seq['COMMENT']
23
+
24
+ options = []
25
+
26
+ options << "start: #{start_value}" if start_value && Integer(start_value) != 1
27
+
28
+ options << "increment: #{increment}" if increment && Integer(increment) != 1
29
+
30
+ options << "cache: #{cache_value}" if cache_value && Integer(cache_value) != 0
31
+
32
+ options << "min_value: #{min_value}" if min_value
33
+
34
+ options << 'cycle: true' if cycle.to_i != 0
35
+
36
+ options << "comment: #{comment.inspect}" if comment.present?
37
+
38
+ statement = [
39
+ 'create_sequence',
40
+ seq['SEQUENCE_NAME'].inspect
41
+ ].join(' ')
42
+
43
+ if options.any?
44
+ statement << (options.any? ? ", #{options.join(', ')}" : '')
45
+ end
46
+
47
+ stream.puts " #{statement}"
48
+ end
49
+
50
+ stream.puts
51
+ end
52
+ end
53
+ end
54
+ end
data/lib/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecord
4
- TIDB_ADAPTER_VERSION = '0.3.0'
4
+ TIDB_ADAPTER_VERSION = '0.4.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-tidb-adapter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hooopo Wang
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-08-12 00:00:00.000000000 Z
11
+ date: 2021-08-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -59,6 +59,11 @@ files:
59
59
  - lib/active_record/connection_adapters/tidb/setup.rb
60
60
  - lib/active_record/connection_adapters/tidb/type.rb
61
61
  - lib/active_record/connection_adapters/tidb_adapter.rb
62
+ - lib/active_record/sequence.rb
63
+ - lib/active_record/sequence/adapter.rb
64
+ - lib/active_record/sequence/command_recorder.rb
65
+ - lib/active_record/sequence/model_methods.rb
66
+ - lib/active_record/sequence/schema_dumper.rb
62
67
  - lib/activerecord-tidb-adapter.rb
63
68
  - lib/version.rb
64
69
  homepage: https://github.com/pingcap/activerecord-tidb-adapter