sequel-sequence 0.2.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,178 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mysql
4
+ def check_sequences
5
+ fetch('SELECT * FROM mysql_sequence;').all.to_a
6
+ end
7
+
8
+ def custom_sequence?(sequence_name)
9
+ out = nil
10
+ begin
11
+ fetch(select_from_mysql_sequence_where(stringify(sequence_name))) do |row|
12
+ out = row[:name]
13
+ end
14
+ rescue Sequel::DatabaseError
15
+ return false
16
+ end
17
+
18
+ !out.nil?
19
+ end
20
+
21
+ def create_sequence(name, options = {})
22
+ check_options(options)
23
+ if_exists = build_exists_condition(options[:if_exists])
24
+ start_option = options[:start] || 1
25
+ num_label = options[:numeric_label] || 0
26
+ return if (current = lastval(name)) && (current >= start_option)
27
+
28
+ run create_sequence_table(stringify(name), if_exists)
29
+ run insert_into_sequence_table_init_values(stringify(name), start_option, num_label)
30
+ run create_mysql_sequence
31
+ table_matcher { run delete_from_mysql_sequence(stringify(name)) }
32
+ run insert_into_mysql_sequence(stringify(name), start_option)
33
+ end
34
+
35
+ def drop_sequence(name, options = {})
36
+ if_exists = build_exists_condition(options[:if_exists])
37
+ run drop_sequence_table(stringify(name), if_exists)
38
+ table_matcher { run delete_from_mysql_sequence(stringify(name)) }
39
+ end
40
+
41
+ def nextval(name)
42
+ run insert_into_sequence_table(stringify(name), 0)
43
+ table_matcher { run delete_from_mysql_sequence(stringify(name)) }
44
+ run insert_last_insert_id_into_mysql_sequence(stringify(name))
45
+ take_seq(stringify(name))
46
+ end
47
+
48
+ def nextval_with_label(name, num_label = 0)
49
+ run insert_into_sequence_table(stringify(name), num_label)
50
+ table_matcher { run delete_from_mysql_sequence(stringify(name)) }
51
+ run insert_last_insert_id_into_mysql_sequence(stringify(name))
52
+ take_seq(stringify(name))
53
+ end
54
+
55
+ def lastval(name)
56
+ take_seq(stringify(name))
57
+ end
58
+
59
+ alias currval lastval
60
+
61
+ def setval(name, value)
62
+ current = lastval(stringify(name))
63
+ if current.nil?
64
+ create_sequence(stringify(name), { start: value })
65
+ elsif value < current
66
+ log_info Sequel::Database::DANGER_OPT_ID
67
+ value = current
68
+ elsif value > current
69
+ run insert_into_sequence_table_init_values(stringify(name), value, 0)
70
+ table_matcher { run delete_from_mysql_sequence(stringify(name)) }
71
+ run insert_into_mysql_sequence(stringify(name), value)
72
+ end
73
+ value
74
+ end
75
+
76
+ def set_column_default_nextval(table, column, sequence)
77
+ run create_sequenced_column(stringify(table),
78
+ stringify(column),
79
+ stringify(sequence))
80
+ run update_sequenced_column(stringify(table),
81
+ stringify(column),
82
+ stringify(sequence))
83
+ end
84
+
85
+ private
86
+
87
+ def stringify(name)
88
+ @name ||= {}
89
+ @name.fetch(name, nil) || (@name[name] = name.to_s)
90
+ end
91
+
92
+ def select_from_mysql_sequence_where(name)
93
+ "SELECT * FROM mysql_sequence where name = '#{name}';"
94
+ end
95
+
96
+ def create_sequence_table(name, if_exists = nil)
97
+ %(
98
+ CREATE TABLE #{if_exists || Sequel::Database::IF_NOT_EXISTS} #{name}
99
+ (id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
100
+ fiction INT);
101
+ ).strip
102
+ end
103
+
104
+ def insert_into_sequence_table_init_values(name, start_id, num_label)
105
+ "INSERT INTO #{name} (id, fiction) VALUES (#{start_id}, #{num_label});"
106
+ end
107
+
108
+ def create_mysql_sequence
109
+ %(
110
+ CREATE TABLE #{Sequel::Database::IF_NOT_EXISTS} mysql_sequence
111
+ (name VARCHAR(40), seq INT);
112
+ ).strip
113
+ end
114
+
115
+ def select_max_seq(name)
116
+ "SELECT MAX(seq) AS id FROM mysql_sequence WHERE name = '#{name}';"
117
+ end
118
+
119
+ def take_seq(name)
120
+ table_matcher do
121
+ out = nil
122
+ fetch(select_max_seq(name)) do |row|
123
+ out = row[:id]
124
+ end
125
+ out
126
+ end
127
+ end
128
+
129
+ def delete_from_mysql_sequence(name)
130
+ "DELETE QUICK IGNORE FROM mysql_sequence WHERE name = '#{name}';"
131
+ end
132
+
133
+ def insert_into_mysql_sequence(name, value)
134
+ "INSERT INTO mysql_sequence (name, seq) VALUES ('#{name}', #{value});"
135
+ end
136
+
137
+ def drop_sequence_table(name, if_exists = nil)
138
+ "DROP TABLE #{if_exists || Sequel::Database::IF_EXISTS} #{name};"
139
+ end
140
+
141
+ def insert_into_sequence_table(name, num_label)
142
+ "INSERT INTO #{name} (fiction) VALUES (#{num_label});"
143
+ end
144
+
145
+ def insert_last_insert_id_into_mysql_sequence(name)
146
+ "INSERT INTO mysql_sequence (name, seq) VALUES ('#{name}', LAST_INSERT_ID());"
147
+ end
148
+
149
+ def create_sequenced_column(table, _column, sequence)
150
+ %(
151
+ CREATE TRIGGER IF NOT EXISTS #{table}_#{sequence} BEFORE INSERT
152
+ ON #{table}
153
+ FOR EACH ROW BEGIN
154
+ DELETE QUICK IGNORE FROM mysql_sequence WHERE name = '#{sequence}';
155
+ INSERT INTO #{sequence} SET fiction = 0;
156
+ INSERT INTO mysql_sequence SET name = '#{sequence}', seq = LAST_INSERT_ID();
157
+
158
+ END;
159
+ ).strip
160
+ end
161
+
162
+ def update_sequenced_column(table, column, sequence)
163
+ %(
164
+ CREATE TRIGGER IF NOT EXISTS #{table}_#{column} BEFORE INSERT
165
+ ON #{table}
166
+ FOR EACH ROW FOLLOWS #{table}_#{sequence}
167
+ SET NEW.#{column} = ( SELECT MAX(seq) FROM mysql_sequence WHERE name = '#{sequence}' );
168
+ ).strip
169
+ end
170
+
171
+ def table_matcher(&block)
172
+ block.call
173
+ rescue Sequel::DatabaseError => e
174
+ return if e.message =~ /\AMysql2::Error: Table(.)*doesn't exist\z/
175
+
176
+ raise e
177
+ end
178
+ end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ # https://sequel.jeremyevans.net/rdoc/files/doc/sql_rdoc.html
4
+ # https://github.com/jeremyevans/sequel/blob/master/lib/sequel/database/connecting.rb
5
+ module Sequel
6
+ module Sequence
7
+ module Database
8
+ module SQLite
9
+ def check_sequences
10
+ fetch('SELECT * FROM `sqlite_sequence`;').all.to_a
11
+ end
12
+
13
+ def create_sequence(name, options = {})
14
+ check_options(options)
15
+ if_exists = build_exists_condition(options[:if_exists])
16
+ start_option = options[:start] || 1
17
+ num_label = options[:numeric_label] || 0
18
+ return if (current = lastval(name)) && (current >= start_option)
19
+
20
+ sql = [create_sequence_table(stringify(name), if_exists)]
21
+ sql << insert_into_sequence_table_init_values(stringify(name), start_option, num_label)
22
+ run(sql.join("\n"))
23
+ end
24
+
25
+ def drop_sequence(name, options = {})
26
+ if_exists = build_exists_condition(options[:if_exists])
27
+ run(drop_sequence_table(stringify(name), if_exists))
28
+ end
29
+
30
+ def nextval(name)
31
+ run(insert_into_sequence_table(stringify(name), 0))
32
+ take_seq(stringify(name))
33
+ end
34
+
35
+ def nextval_with_label(name, num_label = 0)
36
+ run(insert_into_sequence_table(stringify(name), num_label))
37
+ take_seq(stringify(name))
38
+ end
39
+
40
+ def lastval(name)
41
+ take_seq(stringify(name))
42
+ end
43
+
44
+ alias currval lastval
45
+
46
+ def setval(name, value)
47
+ current = lastval(stringify(name))
48
+ if current.nil?
49
+ create_sequence(stringify(name), { start: value })
50
+ elsif value < current
51
+ # sql = [delete_from_sqlite_sequence(name)]
52
+ # sql << drop_sequence_table(name)
53
+ # sql << insert_into_sqlite_sequence(name, value)
54
+ # run(sql.join("\n"))
55
+ log_info DANGER_OPT_ID
56
+ value = current
57
+ else
58
+ run(insert_into_sqlite_sequence(stringify(name), value))
59
+ end
60
+ value
61
+ end
62
+
63
+ def set_column_default_nextval(table, column, sequence)
64
+ run(create_sequenced_column(stringify(table),
65
+ stringify(column),
66
+ stringify(sequence)))
67
+ end
68
+
69
+ private
70
+
71
+ def stringify(name)
72
+ @name ||= {}
73
+ @name.fetch(name, nil) || (@name[name] = name.to_s)
74
+ end
75
+
76
+ def take_seq(name)
77
+ out = nil
78
+ fetch(select_max_seq(name)) do |row|
79
+ out = row[:id]
80
+ end
81
+ out
82
+ end
83
+
84
+ def create_sequence_table(name, if_exists = nil)
85
+ %(
86
+ CREATE TABLE #{if_exists || IF_NOT_EXISTS} #{name}
87
+ (id integer primary key autoincrement, fiction integer);
88
+ )
89
+ end
90
+
91
+ def insert_into_sequence_table_init_values(name, start_id, num_label)
92
+ "INSERT INTO #{name} (id, fiction) VALUES (#{start_id}, #{num_label});"
93
+ end
94
+
95
+ def insert_into_sequence_table(name, num_label)
96
+ "INSERT INTO #{name} (fiction) VALUES (#{num_label});"
97
+ end
98
+
99
+ def insert_into_sqlite_sequence(name, value)
100
+ current = take_seq(name)
101
+ if current.nil?
102
+ "INSERT INTO sqlite_sequence (name, seq) VALUES ('#{name}', #{value});"
103
+ else
104
+ %(
105
+ UPDATE sqlite_sequence
106
+ SET seq = #{[current, value].max}
107
+ WHERE name = '#{name}';
108
+ )
109
+ end
110
+ end
111
+
112
+ def delete_from_sqlite_sequence(name)
113
+ "DELETE FROM sqlite_sequence WHERE name = '#{name}';"
114
+ end
115
+
116
+ def drop_sequence_table(name, if_exists = nil)
117
+ "DROP TABLE #{if_exists || IF_EXISTS} #{name};"
118
+ end
119
+
120
+ def select_max_seq(name)
121
+ "SELECT MAX(seq) AS id FROM sqlite_sequence WHERE name = '#{name}';"
122
+ end
123
+
124
+ def create_sequenced_column(table, column, sequence)
125
+ %(
126
+ CREATE TRIGGER IF NOT EXISTS #{table}_#{sequence} AFTER INSERT
127
+ ON #{table}
128
+ BEGIN
129
+ INSERT INTO #{sequence} (fiction) VALUES (0);
130
+ UPDATE #{table}
131
+ SET #{column} = (SELECT MAX(seq) FROM sqlite_sequence WHERE name = '#{sequence}')
132
+ WHERE rowid = NEW.rowid;
133
+ END;
134
+ )
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -3,6 +3,16 @@
3
3
  module Sequel
4
4
  module Sequence
5
5
  module Database
6
+ DANGER_OPT_ID = "Warning! The new sequence ID can't be less than the current one."
7
+ DANGER_OPT_INCREMENT = 'Warning! Increments greater than 1 are not supported.'
8
+ IF_EXISTS = 'IF EXISTS'
9
+ IF_NOT_EXISTS = 'IF NOT EXISTS'
10
+
11
+ def check_options(params)
12
+ log_info DANGER_OPT_INCREMENT if params[:increment] && params[:increment] != 1
13
+ log_info DANGER_OPT_INCREMENT if params[:step] && params[:step] != 1
14
+ end
15
+
6
16
  def custom_sequence?(_sequence_name)
7
17
  raise Sequel::MethodNotAllowed, Sequel::MethodNotAllowed::METHOD_NOT_ALLOWED
8
18
  end
@@ -15,7 +25,7 @@ module Sequel
15
25
  raise Sequel::MethodNotAllowed, Sequel::MethodNotAllowed::METHOD_NOT_ALLOWED
16
26
  end
17
27
 
18
- def drop_sequence(_name)
28
+ def drop_sequence(_name, _options = {})
19
29
  raise Sequel::MethodNotAllowed, Sequel::MethodNotAllowed::METHOD_NOT_ALLOWED
20
30
  end
21
31
 
@@ -35,21 +45,34 @@ module Sequel
35
45
  name.to_s.split('.', 2).map { |part| quote_sequence_name(part) }.join('.')
36
46
  end
37
47
 
48
+ def nextval_with_label(_name, _num_label = 0)
49
+ raise Sequel::MethodNotAllowed, Sequel::MethodNotAllowed::METHOD_NOT_ALLOWED
50
+ end
51
+
38
52
  def nextval(_name)
39
53
  raise Sequel::MethodNotAllowed, Sequel::MethodNotAllowed::METHOD_NOT_ALLOWED
40
54
  end
41
55
 
42
- # for connection.adapter_name = "PostgreSQL"
56
+ # for Postgres
43
57
  def currval(_name)
44
58
  raise Sequel::MethodNotAllowed, Sequel::MethodNotAllowed::METHOD_NOT_ALLOWED
45
59
  end
46
60
 
47
- # for connection.adapter_name = "Mysql2"
61
+ # for MariaDB
48
62
  alias lastval currval
49
63
 
50
64
  def setval(_name, _value)
51
65
  raise Sequel::MethodNotAllowed, Sequel::MethodNotAllowed::METHOD_NOT_ALLOWED
52
66
  end
67
+
68
+ def build_exists_condition(option)
69
+ case option
70
+ when true
71
+ IF_EXISTS
72
+ when false
73
+ IF_NOT_EXISTS
74
+ end
75
+ end
53
76
  end
54
77
  end
55
78
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'database/server/mysql'
4
+ require_relative 'database/server/mariadb'
5
+
6
+ module Sequel
7
+ class Database
8
+ class << self
9
+ attr_reader :dbms
10
+ end
11
+
12
+ old_connect = singleton_method(:connect)
13
+
14
+ define_singleton_method(:connect) do |*args|
15
+ db = old_connect.call(*args)
16
+ if db.adapter_scheme == :mysql2
17
+ @dbms = db.mariadb? ? Mariadb : Mysql
18
+ puts "Sequel::Database.REconnect mariadb? = #{db.mariadb?.inspect}"
19
+ puts "Sequel::Database.REconnect server_version = #{db.server_version.inspect}"
20
+ Sequel::Mysql2::Database.include(@dbms)
21
+ end
22
+ db
23
+ end
24
+ end
25
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Sequel
4
4
  module Sequence
5
- VERSION = '0.2.0'
5
+ VERSION = '0.4.0'
6
6
  end
7
7
  end
@@ -2,9 +2,10 @@
2
2
 
3
3
  require 'sequel/database'
4
4
  require 'sequel/adapters/postgres'
5
- # require 'sequel/adapters/mysql'
6
5
  require 'sequel/adapters/mysql2'
6
+ require 'sequel/adapters/sqlite'
7
7
  require 'sequel/error'
8
+ require 'sequel/sequence/database_ext_connection'
8
9
 
9
10
  module Sequel
10
11
  module Sequence
@@ -12,8 +13,7 @@ module Sequel
12
13
 
13
14
  module Database
14
15
  require 'sequel/sequence/database/postgresql'
15
- # require "sequel/sequence/database/mysql"
16
- require 'sequel/sequence/database/mysql2'
16
+ require 'sequel/sequence/database/sqlite'
17
17
  end
18
18
  end
19
19
  end
@@ -24,9 +24,6 @@ Sequel::Database.include(
24
24
  Sequel::Postgres::Database.include(
25
25
  Sequel::Sequence::Database::PostgreSQL
26
26
  )
27
- Sequel::Mysql2::Database.include(
28
- Sequel::Sequence::Database::Mysql2
27
+ Sequel::SQLite::Database.include(
28
+ Sequel::Sequence::Database::SQLite
29
29
  )
30
- # Sequel::Mysql::Database.include(
31
- # Sequel::Sequence::Database::Mysql
32
- # )
@@ -7,10 +7,11 @@ Gem::Specification.new do |spec|
7
7
  spec.version = Sequel::Sequence::VERSION
8
8
  spec.licenses = ['MIT']
9
9
  spec.summary = \
10
- "Add support for PostgreSQL's and MySQL's SEQUENCE on Sequel migrations."
10
+ 'Adds SEQUENCE support to Sequel for migrations to PostgreSQL, MariaDB, MySQL and SQLite.'
11
11
  spec.description = <<-DES
12
12
  This gem provides a single interface for SEQUENCE functionality
13
- in Postgresql and Mysql databases within the Sequel ORM.
13
+ in Postgresql and MariaDB DBMS within the Sequel ORM.
14
+ It also models the Sequences to meet the needs of SQLite and MySQL users.
14
15
  DES
15
16
  spec.authors = ['Nikolai Bocharov']
16
17
  spec.email = ['it.architect@yahoo.com']
@@ -37,9 +38,11 @@ Gem::Specification.new do |spec|
37
38
  # JRuby Adapter Dependencies
38
39
  spec.add_development_dependency 'jdbc-mysql', '~> 8.0.17'
39
40
  spec.add_development_dependency 'jdbc-postgres', '~> 42.2.14'
41
+ spec.add_development_dependency 'jdbc-sqlite3', '~> 3.42'
40
42
  else
41
43
  # MRI/Rubinius Adapter Dependencies
42
44
  spec.add_development_dependency 'mysql2', '~> 0.5.3'
43
45
  spec.add_development_dependency 'pg', '~> 1.5.4'
46
+ spec.add_development_dependency 'sqlite3', '~> 1.6.0'
44
47
  end
45
48
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ MariaDB = Sequel.connect(
6
+ adapter: 'mysql2',
7
+ user: ENV['TEST_MARIA_USERNAME'] || 'root',
8
+ password: ENV['TEST_MARIA_PASSWORD'] || 'root',
9
+ host: ENV['TEST_MARIA_HOST'] || '127.0.0.1',
10
+ port: ENV['TEST_MARIA_PORT'] || 3306,
11
+ database: ENV['TEST_MARIA_DATABASE'] || 'test'
12
+ )
13
+
14
+ module MariadbTestHelper
15
+ def recreate_table
16
+ MariaDB.run 'DROP SEQUENCE IF EXISTS position'
17
+ MariaDB.run 'DROP TABLE IF EXISTS wares'
18
+ MariaDB.run 'DROP SEQUENCE IF EXISTS a'
19
+ MariaDB.run 'DROP SEQUENCE IF EXISTS b'
20
+ MariaDB.run 'DROP SEQUENCE IF EXISTS c'
21
+ sql = 'CREATE TABLE wares (id INT AUTO_INCREMENT, slug VARCHAR(255), quantity INT DEFAULT(0), PRIMARY KEY(id));'
22
+ MariaDB.run sql
23
+ end
24
+
25
+ def with_migration(&block)
26
+ migration_class = Sequel::Migration
27
+
28
+ Sequel::Model.db = MariaDB
29
+
30
+ Class.new(migration_class, &block).new(MariaDB)
31
+ end
32
+ end
@@ -5,20 +5,22 @@ require 'test_helper'
5
5
  MysqlDB = Sequel.connect(
6
6
  adapter: 'mysql2',
7
7
  user: ENV['TEST_MYSQL_USERNAME'] || 'root',
8
- password: ENV['TEST_MYSQL_PASSWORD'] || 'root',
9
- host: ENV['TEST_MYSQL_HOST'] || '127.0.0.1',
10
- port: ENV['TEST_MYSQL_PORT'] || 3306,
8
+ password: ENV['TEST_MYSQL_PASSWORD'] || 'rootroot',
9
+ host: ENV['TEST_MYSQL_HOST'] || '0.0.0.0',
10
+ port: ENV['TEST_MYSQL_PORT'] || 3360,
11
11
  database: ENV['TEST_MYSQL_DATABASE'] || 'test'
12
12
  )
13
13
 
14
14
  module MysqlTestHelper
15
15
  def recreate_table
16
- MysqlDB.run 'DROP SEQUENCE IF EXISTS position'
17
- MysqlDB.run 'DROP TABLE IF EXISTS wares'
18
- MysqlDB.run 'DROP SEQUENCE IF EXISTS a'
19
- MysqlDB.run 'DROP SEQUENCE IF EXISTS b'
20
- MysqlDB.run 'DROP SEQUENCE IF EXISTS c'
21
- sql = 'CREATE TABLE wares (id INT AUTO_INCREMENT, slug VARCHAR(255), quantity INT DEFAULT(0), PRIMARY KEY(id));'
16
+ MysqlDB.drop_table :creators, if_exists: true
17
+ MysqlDB.drop_sequence :position_id, if_exists: true
18
+ MysqlDB.drop_sequence :position
19
+ MysqlDB.drop_table :stuffs, if_exists: true
20
+ MysqlDB.drop_sequence 'a'
21
+ MysqlDB.drop_sequence 'b'
22
+ MysqlDB.drop_sequence 'c'
23
+ sql = 'CREATE TABLE stuffs (id INT AUTO_INCREMENT PRIMARY KEY, slug VARCHAR(255), quantity INT DEFAULT(0));'
22
24
  MysqlDB.run sql
23
25
  end
24
26