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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aab950fe6ed48722462c348fca86a095c0055315857b1987cd53f47ce8f91a24
4
- data.tar.gz: 4c5a5e110780ac27af1ef46e2eab019ecaaff6886c7701628cd0ae5c219593af
3
+ metadata.gz: 606c2258083d60aa81e7d114894d9b22fff92c33036981ce1053371a2897607b
4
+ data.tar.gz: 3dd8a996e9a7a233eb698bc2ae93b25aafabbf0bac0f156650daac3e911dd3da
5
5
  SHA512:
6
- metadata.gz: eb7f89f857e1cc7feda3f738084d23ed9abe590e287721ec092797341453cfcafe12e1fd673ee0bcc55794a3c766377a30b5b48de31cd2ee9ff2341df962d27d
7
- data.tar.gz: faab68ab131cc4e173d0510d5a7aa07798c9bee2fe009f6b5b110ba601693e7947e7af907f280952e0a264acc599184e4b770f46a27ced2fb9d41cdb642dc974
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
- class Database
14
- include Singleton
13
+ module Database
15
14
  attr_reader :db
16
-
17
15
  ##
18
16
  # Ищем секцию настроек Cfg.db
19
- def initialize
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.info{ "БД #{ Cfg.db.database }." }
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.1.1
4
+ version: 1.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - deemytch
8
- autorequire:
9
- bindir: bin
8
+ autorequire:
9
+ bindir: utils
10
10
  cert_chain: []
11
- date: 2020-05-26 00:00:00.000000000 Z
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
- description:
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
- homepage: https://gitlab.sudo.su/amqp-lib/cfgdatabase
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.1.2
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: []