sequel-sequence 0.2.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.
@@ -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