cfgdatabase 1.1.1 → 1.2.5
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/lib/app-database.rb +15 -6
- data/utils/migrate.rb +186 -0
- data/utils/migrations/00000000000000_created_at_trigger.rb +26 -0
- metadata +28 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 606c2258083d60aa81e7d114894d9b22fff92c33036981ce1053371a2897607b
|
4
|
+
data.tar.gz: 3dd8a996e9a7a233eb698bc2ae93b25aafabbf0bac0f156650daac3e911dd3da
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7f271b2a238c361ee4e497b0bcde8dc6650ea2aa370c266f906dd225cf67b8b79f5df168e294baae2dec22ddb5dfb27d52b04232bc3cd24b2afc79f86e0e7c38
|
7
|
+
data.tar.gz: b444cc1b1bb12fbb7ee69f8d18ab820e95fe5dda08ea8a1e6316e46b09688ab776526fc9c8ca6333e55de125b7ac33b5d292f8431ad33873a454c9cdbe848dca
|
data/lib/app-database.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
+
require 'pg'
|
1
2
|
require 'sequel'
|
2
|
-
require 'singleton'
|
3
3
|
require 'app-config'
|
4
4
|
require 'app-logger'
|
5
5
|
|
@@ -10,16 +10,15 @@ require 'app-logger'
|
|
10
10
|
# Предполагается использование глобальной Cfg для настроек
|
11
11
|
|
12
12
|
module App
|
13
|
-
|
14
|
-
include Singleton
|
13
|
+
module Database
|
15
14
|
attr_reader :db
|
16
|
-
|
17
15
|
##
|
18
16
|
# Ищем секцию настроек Cfg.db
|
19
|
-
|
17
|
+
module_function
|
18
|
+
def init
|
20
19
|
raise ArgumentError.new("Cfg not found!") if ! defined?( ::Cfg ) || ! Cfg.db || Cfg.db.empty?
|
21
20
|
if (! defined? @db ) || ( @db.nil? ) || ( ! @db ) || ( ! @db.test_connection )
|
22
|
-
Log.
|
21
|
+
Log.debug{ "БД #{ Cfg.db.database }." }
|
23
22
|
Sequel.extension :pg_array, :pg_inet, :pg_json, :pg_json_ops, :pg_array, :pg_array_ops, :pg_row, :pg_hstore, :pg_json_ops
|
24
23
|
Sequel::Model.raise_on_save_failure = false
|
25
24
|
Sequel::Model.plugin :validation_helpers
|
@@ -39,5 +38,15 @@ module App
|
|
39
38
|
return @db
|
40
39
|
end
|
41
40
|
|
41
|
+
def remove
|
42
|
+
if defined?( Db )
|
43
|
+
Db.disconnect if @db.test_connection
|
44
|
+
Kernel.send :remove_const, 'Db'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def connected?
|
49
|
+
!! ( defined?( Db ) && Db.test_connection )
|
50
|
+
end
|
42
51
|
end
|
43
52
|
end
|
data/utils/migrate.rb
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'pathname'
|
7
|
+
require 'bunny'
|
8
|
+
require 'logger'
|
9
|
+
require 'yaml'
|
10
|
+
require 'optparse'
|
11
|
+
require 'thor'
|
12
|
+
require 'monkey-hash'
|
13
|
+
require 'app-config'
|
14
|
+
require 'app-logger'
|
15
|
+
require 'sequel'
|
16
|
+
require 'pg'
|
17
|
+
require 'app-database'
|
18
|
+
|
19
|
+
App::Config.init( approot: Pathname.new( Dir.pwd ).expand_path )
|
20
|
+
App::Logger.new
|
21
|
+
App::Database.init if Cfg.db?
|
22
|
+
Sequel.extension :migration
|
23
|
+
|
24
|
+
# тупой способ определения запуска в корне проекта
|
25
|
+
launchdir = Dir.pwd
|
26
|
+
if launchdir != Cfg.root || ! File.exist?( "#{ Cfg.root }/Gemfile" )
|
27
|
+
puts "\n\tВнимание! Следует запускать migrate.rb в корне проекта.\n\n"
|
28
|
+
raise "\n\tВнимание! Следует запускать migrate.rb в корне проекта.\n\n"
|
29
|
+
end
|
30
|
+
|
31
|
+
class Dbtask < Thor
|
32
|
+
package_name 'db'
|
33
|
+
desc 'init', 'Создать папки и записать нулевую миграцию'
|
34
|
+
def init
|
35
|
+
mydir = Pathname( __dir__ ).expand_path.to_s
|
36
|
+
migrations = Pathname( "#{ Cfg.root }/db/migrations" ).expand_path.to_s
|
37
|
+
Log.info{"Создаю папку #{ migrations }"}
|
38
|
+
FileUtils.mkdir_p migrations
|
39
|
+
list = Dir[ "#{ mydir }/migrations/*rb" ]
|
40
|
+
Log.info{"Копирую файл#{ list.count == 1 ? '' : 'ы' } с миграциями:\n#{ list.join("\n") }"}
|
41
|
+
FileUtils.cp list, "#{ migrations }/"
|
42
|
+
end
|
43
|
+
|
44
|
+
desc "g class_name", "Создать файл миграции в db/migrations"
|
45
|
+
def g(class_name)
|
46
|
+
tstamp = Time.now.strftime "%Y%m%d%H%M%S"
|
47
|
+
fname = ''
|
48
|
+
if Dir[ "#{ Cfg.root }/db/migrations/#{ tstamp }_*.rb" ].any?
|
49
|
+
counter = 0
|
50
|
+
while Dir[ "#{ Cfg.root }/db/migrations/#{ tstamp }#{ '%02d' % counter }_*.rb" ].any? do
|
51
|
+
counter += 1
|
52
|
+
end
|
53
|
+
fname = "#{ Cfg.root }/db/migrations/#{ tstamp }_#{ '%02d' % counter }_#{ class_name }.rb"
|
54
|
+
else
|
55
|
+
fname = "#{ Cfg.root }/db/migrations/#{ tstamp }_#{ class_name }.rb"
|
56
|
+
end
|
57
|
+
Log.info{ "Создаю файлик миграции #{ fname }" }
|
58
|
+
File.open( fname, 'w' ) do |f|
|
59
|
+
f.write <<~EFILE
|
60
|
+
Sequel.migration do
|
61
|
+
up do
|
62
|
+
create_table :#{ class_name } do
|
63
|
+
primary_key :id, type: :Bignum
|
64
|
+
|
65
|
+
column :created_at, DateTime, null: false, index: true, default: Sequel.lit("now()")
|
66
|
+
column :updated_at, DateTime, null: false, index: true, default: Sequel.lit("now()")
|
67
|
+
end
|
68
|
+
run <<~EUP
|
69
|
+
DO $$
|
70
|
+
BEGIN
|
71
|
+
--triggers
|
72
|
+
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = '#{ class_name }_update_timestamp') THEN
|
73
|
+
CREATE TRIGGER #{ class_name }_update_timestamp
|
74
|
+
BEFORE INSERT OR UPDATE ON #{ class_name }
|
75
|
+
FOR EACH ROW EXECUTE PROCEDURE update_timestamp();
|
76
|
+
END IF;
|
77
|
+
END $$;
|
78
|
+
EUP
|
79
|
+
end
|
80
|
+
down { run 'DROP TABLE #{ class_name } CASCADE' }
|
81
|
+
end
|
82
|
+
EFILE
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
desc "m", "Мигрировать миграции, можно добавить имя файла"
|
87
|
+
def m(point = nil)
|
88
|
+
point = point_from_filename(point) || nil
|
89
|
+
Log.warn{ "Мигрирую базу #{ point }" }
|
90
|
+
if point
|
91
|
+
Sequel::Migrator.run(Db, "db/migrations", target: point )
|
92
|
+
else
|
93
|
+
Sequel::Migrator.run(Db, "db/migrations" )
|
94
|
+
end
|
95
|
+
v
|
96
|
+
end
|
97
|
+
|
98
|
+
desc "r", "Откатить базу в ноль, либо к указанному файлу"
|
99
|
+
def r(point = '0')
|
100
|
+
point = point_from_filename(point) || 0
|
101
|
+
Log.warn{ "Откатываю базу #{ point }" }
|
102
|
+
if point
|
103
|
+
Sequel::Migrator.run(Db, "db/migrations", :target => point )
|
104
|
+
else
|
105
|
+
Sequel::Migrator.run(Db, "db/migrations" )
|
106
|
+
end
|
107
|
+
v
|
108
|
+
end
|
109
|
+
|
110
|
+
desc "v" , "Напечатать текущую версию в базе"
|
111
|
+
def v
|
112
|
+
version =
|
113
|
+
if Db.tables.include?(:schema_migrations)
|
114
|
+
(f = Db[:schema_migrations].all).any? ? f.last[:filename] : 'пусто'
|
115
|
+
else
|
116
|
+
'пусто'
|
117
|
+
end
|
118
|
+
puts "Последняя миграция: #{ version }"
|
119
|
+
end
|
120
|
+
|
121
|
+
desc "create", "Создать базу"
|
122
|
+
def create
|
123
|
+
rootdb = superdb
|
124
|
+
Log.warn{ "Создаю пользователя: #{ Cfg.db.user } и базу: #{ Cfg.db.database }." }
|
125
|
+
begin
|
126
|
+
rootdb["CREATE USER #{ Cfg.db.user } WITH LOGIN PASSWORD '#{ Cfg.db.password }'"].all
|
127
|
+
rescue Exception => e
|
128
|
+
Log.info{ e.message }
|
129
|
+
end
|
130
|
+
begin
|
131
|
+
rootdb["CREATE DATABASE #{ Cfg.db.database } OWNER #{ Cfg.db.user }"].all
|
132
|
+
rescue Exception => e
|
133
|
+
Log.info{ e.message }
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
desc "scratch", 'Удалить базу, загрузить все миграции заново'
|
138
|
+
def scratch(db = nil)
|
139
|
+
db = superdb
|
140
|
+
Log.debug{"Отключение от текущей базы"}
|
141
|
+
Db.disconnect
|
142
|
+
unless db.test_connection
|
143
|
+
Log.warn{"Не смог подключиться к базе для махинаций. #{ superuser.inspect }"}
|
144
|
+
exit 255
|
145
|
+
end
|
146
|
+
Log.warn{ "Удаляю базу #{ Cfg.db.database }" }
|
147
|
+
begin
|
148
|
+
db << "DROP DATABASE #{ Cfg.db.database }"
|
149
|
+
rescue Exception => e
|
150
|
+
Log.error e.message
|
151
|
+
end
|
152
|
+
create
|
153
|
+
m
|
154
|
+
end
|
155
|
+
|
156
|
+
no_commands do
|
157
|
+
# подключается к базе с правами админа
|
158
|
+
# жёстко закодировано, что админ базы 'postgres' без пароля, и схема 'public'
|
159
|
+
def superdb
|
160
|
+
if ! @rootdb || ! @rootdb.test_connection
|
161
|
+
superuser = Marshal.load(Marshal.dump( Cfg.db ))
|
162
|
+
superuser[:adapter] = 'postgres'
|
163
|
+
superuser[:user] = 'postgres'
|
164
|
+
superuser[:database] = 'postgres'
|
165
|
+
superuser.delete :password
|
166
|
+
Log.debug{"Попытка административного подключения #{ superuser.inspect }"}
|
167
|
+
@rootdb = Sequel.connect( superuser )
|
168
|
+
end
|
169
|
+
@rootdb
|
170
|
+
end
|
171
|
+
|
172
|
+
def point_from_filename(n)
|
173
|
+
Log.debug{"Поиск миграции по куску имени: '#{ n }'."}
|
174
|
+
return nil unless n
|
175
|
+
unless n =~ /^\d+/
|
176
|
+
Pathname.new( Dir["#{ Cfg.root }/db/migrations/*#{ n }*.rb"].sort.last ).basename.to_s[/(\d+)/, 1].to_i
|
177
|
+
else
|
178
|
+
n[/^(\d+)/, 1].to_i
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
Dbtask.start
|
@@ -0,0 +1,26 @@
|
|
1
|
+
Sequel.migration do
|
2
|
+
up do
|
3
|
+
run <<~ESTOREDPROC
|
4
|
+
CREATE OR REPLACE FUNCTION insert_timestamp() RETURNS trigger
|
5
|
+
LANGUAGE plpgsql
|
6
|
+
AS $$
|
7
|
+
BEGIN
|
8
|
+
IF NEW.created_at IS NULL THEN
|
9
|
+
NEW.created_at := now();
|
10
|
+
END IF;
|
11
|
+
RETURN NEW;
|
12
|
+
END $$;
|
13
|
+
|
14
|
+
CREATE OR REPLACE FUNCTION update_timestamp() RETURNS trigger
|
15
|
+
LANGUAGE plpgsql
|
16
|
+
AS $$
|
17
|
+
BEGIN
|
18
|
+
IF NEW.updated_at IS NULL THEN
|
19
|
+
NEW.updated_at := now();
|
20
|
+
END IF;
|
21
|
+
RETURN NEW;
|
22
|
+
END $$;
|
23
|
+
ESTOREDPROC
|
24
|
+
end
|
25
|
+
down { run 'DROP FUNCTION IF EXISTS insert_timestamp(); DROP FUNCTION IF EXISTS update_timestamp();' }
|
26
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cfgdatabase
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- deemytch
|
8
|
-
autorequire:
|
9
|
-
bindir:
|
8
|
+
autorequire:
|
9
|
+
bindir: utils
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-03-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sequel
|
@@ -52,18 +52,35 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
-
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pg
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description:
|
56
70
|
email: aspamkiller@yandex.ru
|
57
|
-
executables:
|
71
|
+
executables:
|
72
|
+
- migrate.rb
|
58
73
|
extensions: []
|
59
74
|
extra_rdoc_files: []
|
60
75
|
files:
|
61
76
|
- lib/app-database.rb
|
62
|
-
|
77
|
+
- utils/migrate.rb
|
78
|
+
- utils/migrations/00000000000000_created_at_trigger.rb
|
79
|
+
homepage: https://github.com/deemytch/cfgdatabase
|
63
80
|
licenses:
|
64
81
|
- GPL-2.0
|
65
82
|
metadata: {}
|
66
|
-
post_install_message:
|
83
|
+
post_install_message:
|
67
84
|
rdoc_options: []
|
68
85
|
require_paths:
|
69
86
|
- lib
|
@@ -78,8 +95,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
78
95
|
- !ruby/object:Gem::Version
|
79
96
|
version: '0'
|
80
97
|
requirements: []
|
81
|
-
rubygems_version: 3.
|
82
|
-
signing_key:
|
98
|
+
rubygems_version: 3.2.13
|
99
|
+
signing_key:
|
83
100
|
specification_version: 4
|
84
|
-
summary: Удобная загрузка настроек Sequel.
|
101
|
+
summary: Удобная загрузка настроек Sequel и миграции для PostgreSQL.
|
85
102
|
test_files: []
|