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 +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +57 -1
- data/Rakefile +1 -1
- data/lib/active_record/connection_adapters/tidb/type.rb +2 -0
- data/lib/active_record/connection_adapters/tidb_adapter.rb +31 -1
- data/lib/active_record/sequence.rb +21 -0
- data/lib/active_record/sequence/adapter.rb +45 -0
- data/lib/active_record/sequence/command_recorder.rb +20 -0
- data/lib/active_record/sequence/model_methods.rb +22 -0
- data/lib/active_record/sequence/schema_dumper.rb +54 -0
- data/lib/version.rb +1 -1
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 85ed982e6735cfcb567e2d863975ba8f56e98dd6243578394e2ca01dc07ca91a
|
4
|
+
data.tar.gz: c7aefb6293698e59ce8ff6812b156070622c6ed7c58689e39c4eca5946309959
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: af4ea51bd264d25cd621e3c95438fd9fd3c7df8b2dea533172cbeb0e07110e661dc68b0d294c23a1420e2b9c0a74334b14c46213d59425764a37336702d17034
|
7
|
+
data.tar.gz: d07ec9fa2f944d6832c7f70287d4583073ea633b7c8364936801e555ac5592a4c5d21b18e9fff5b12a2599b4b8bfc637c5163cbb0b6604e0af1c5b388bc7f70f
|
data/Gemfile.lock
CHANGED
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/
|
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
@@ -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
|
-
|
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
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.
|
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-
|
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
|