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 +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
|