activerecord-tidb-adapter 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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