pakyow-data 1.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +0 -0
- data/LICENSE +4 -0
- data/README.md +29 -0
- data/lib/pakyow/data/adapters/abstract.rb +58 -0
- data/lib/pakyow/data/adapters/sql/commands.rb +58 -0
- data/lib/pakyow/data/adapters/sql/dataset_methods.rb +29 -0
- data/lib/pakyow/data/adapters/sql/differ.rb +76 -0
- data/lib/pakyow/data/adapters/sql/migrator/adapter_methods.rb +95 -0
- data/lib/pakyow/data/adapters/sql/migrator.rb +181 -0
- data/lib/pakyow/data/adapters/sql/migrators/automator.rb +49 -0
- data/lib/pakyow/data/adapters/sql/migrators/finalizer.rb +96 -0
- data/lib/pakyow/data/adapters/sql/runner.rb +49 -0
- data/lib/pakyow/data/adapters/sql/source_extension.rb +31 -0
- data/lib/pakyow/data/adapters/sql/types.rb +50 -0
- data/lib/pakyow/data/adapters/sql.rb +247 -0
- data/lib/pakyow/data/behavior/config.rb +28 -0
- data/lib/pakyow/data/behavior/lookup.rb +75 -0
- data/lib/pakyow/data/behavior/serialization.rb +40 -0
- data/lib/pakyow/data/connection.rb +103 -0
- data/lib/pakyow/data/container.rb +273 -0
- data/lib/pakyow/data/errors.rb +169 -0
- data/lib/pakyow/data/framework.rb +42 -0
- data/lib/pakyow/data/helpers.rb +11 -0
- data/lib/pakyow/data/lookup.rb +85 -0
- data/lib/pakyow/data/migrator.rb +182 -0
- data/lib/pakyow/data/object.rb +98 -0
- data/lib/pakyow/data/proxy.rb +262 -0
- data/lib/pakyow/data/result.rb +53 -0
- data/lib/pakyow/data/sources/abstract.rb +82 -0
- data/lib/pakyow/data/sources/ephemeral.rb +72 -0
- data/lib/pakyow/data/sources/relational/association.rb +43 -0
- data/lib/pakyow/data/sources/relational/associations/belongs_to.rb +47 -0
- data/lib/pakyow/data/sources/relational/associations/has_many.rb +54 -0
- data/lib/pakyow/data/sources/relational/associations/has_one.rb +54 -0
- data/lib/pakyow/data/sources/relational/associations/through.rb +67 -0
- data/lib/pakyow/data/sources/relational/command.rb +531 -0
- data/lib/pakyow/data/sources/relational/migrator.rb +101 -0
- data/lib/pakyow/data/sources/relational.rb +587 -0
- data/lib/pakyow/data/subscribers/adapters/memory.rb +153 -0
- data/lib/pakyow/data/subscribers/adapters/redis/pipeliner.rb +45 -0
- data/lib/pakyow/data/subscribers/adapters/redis/scripts/_shared.lua +73 -0
- data/lib/pakyow/data/subscribers/adapters/redis/scripts/expire.lua +16 -0
- data/lib/pakyow/data/subscribers/adapters/redis/scripts/persist.lua +15 -0
- data/lib/pakyow/data/subscribers/adapters/redis/scripts/register.lua +37 -0
- data/lib/pakyow/data/subscribers/adapters/redis.rb +209 -0
- data/lib/pakyow/data/subscribers.rb +148 -0
- data/lib/pakyow/data/tasks/bootstrap.rake +18 -0
- data/lib/pakyow/data/tasks/create.rake +22 -0
- data/lib/pakyow/data/tasks/drop.rake +32 -0
- data/lib/pakyow/data/tasks/finalize.rake +56 -0
- data/lib/pakyow/data/tasks/migrate.rake +24 -0
- data/lib/pakyow/data/tasks/reset.rake +18 -0
- data/lib/pakyow/data/types.rb +37 -0
- data/lib/pakyow/data.rb +27 -0
- data/lib/pakyow/environment/data/auto_migrate.rb +31 -0
- data/lib/pakyow/environment/data/config.rb +54 -0
- data/lib/pakyow/environment/data/connections.rb +76 -0
- data/lib/pakyow/environment/data/memory_db.rb +23 -0
- data/lib/pakyow/validations/unique.rb +26 -0
- metadata +186 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pakyow
|
4
|
+
module Data
|
5
|
+
module Adapters
|
6
|
+
class Sql
|
7
|
+
class Runner
|
8
|
+
def initialize(connection, migration_path)
|
9
|
+
@connection, @migration_path = connection, migration_path
|
10
|
+
end
|
11
|
+
|
12
|
+
def disconnect!
|
13
|
+
@connection.disconnect
|
14
|
+
end
|
15
|
+
|
16
|
+
def run!
|
17
|
+
Pakyow.module_eval do
|
18
|
+
unless singleton_class.instance_methods.include?(:migration)
|
19
|
+
def self.migration(&block)
|
20
|
+
Sequel.migration(&block)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Allows migrations to be defined with the nice mapping, then executed with the Sequel type.
|
26
|
+
#
|
27
|
+
local_types = @connection.types
|
28
|
+
@connection.adapter.connection.define_singleton_method :type_literal do |column|
|
29
|
+
if column[:type].is_a?(Symbol)
|
30
|
+
begin
|
31
|
+
column[:type] = Data::Types.type_for(column[:type], local_types).meta[:database_type]
|
32
|
+
rescue Pakyow::UnknownType
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
super(column)
|
37
|
+
end
|
38
|
+
|
39
|
+
Sequel.extension :migration
|
40
|
+
Sequel::Migrator.run(
|
41
|
+
@connection.adapter.connection, @migration_path,
|
42
|
+
allow_missing_migration_files: true
|
43
|
+
)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pakyow
|
4
|
+
module Data
|
5
|
+
module Adapters
|
6
|
+
class Sql
|
7
|
+
module SourceExtension
|
8
|
+
extend Support::Extension
|
9
|
+
|
10
|
+
private def build(string, *args)
|
11
|
+
Sequel.lit(string, *args)
|
12
|
+
end
|
13
|
+
|
14
|
+
apply_extension do
|
15
|
+
class_state :dataset_table, default: self.__object_name.name
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def table(table_name)
|
19
|
+
@dataset_table = table_name
|
20
|
+
end
|
21
|
+
|
22
|
+
def default_primary_key_type
|
23
|
+
:bignum
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pakyow
|
4
|
+
module Data
|
5
|
+
module Adapters
|
6
|
+
class Sql
|
7
|
+
module Types
|
8
|
+
module Postgres
|
9
|
+
TYPES = {
|
10
|
+
bignum: Sql::TYPES[:bignum].meta(native_type: "bigint"),
|
11
|
+
decimal: Sql::TYPES[:decimal].meta(column_type: :decimal, native_type: ->(meta) { "numeric(#{meta[:size][0]},#{meta[:size][1]})" }),
|
12
|
+
integer: Sql::TYPES[:integer].meta(native_type: "integer"),
|
13
|
+
string: Sql::TYPES[:string].meta(native_type: "text"),
|
14
|
+
text: Sql::TYPES[:text].meta(column_type: :string),
|
15
|
+
|
16
|
+
json: Pakyow::Data::Types.Constructor(:json) { |value|
|
17
|
+
Sequel.pg_json(value)
|
18
|
+
}.meta(mapping: :json, database_type: :json)
|
19
|
+
}.freeze
|
20
|
+
end
|
21
|
+
|
22
|
+
module SQLite
|
23
|
+
TYPES = {
|
24
|
+
bignum: Sql::TYPES[:bignum].meta(native_type: "bigint"),
|
25
|
+
decimal: Sql::TYPES[:decimal].meta(column_type: :decimal, native_type: ->(meta) { "numeric(#{meta[:size][0]}, #{meta[:size][1]})" }),
|
26
|
+
integer: Sql::TYPES[:integer].meta(native_type: "integer"),
|
27
|
+
string: Sql::TYPES[:string].meta(native_type: "varchar(255)"),
|
28
|
+
text: Sql::TYPES[:text].meta(column_type: :string),
|
29
|
+
|
30
|
+
# Used indirectly for migrations to override the column type (since
|
31
|
+
# sqlite doesn't support bignum as a primary key).
|
32
|
+
#
|
33
|
+
pk_bignum: Sql::TYPES[:bignum].meta(column_type: :integer)
|
34
|
+
}.freeze
|
35
|
+
end
|
36
|
+
|
37
|
+
module MySQL
|
38
|
+
TYPES = {
|
39
|
+
bignum: Sql::TYPES[:bignum].meta(native_type: "bigint(20)"),
|
40
|
+
decimal: Sql::TYPES[:decimal].meta(column_type: :decimal, native_type: ->(meta) { "decimal(#{meta[:size][0]},#{meta[:size][1]})" }),
|
41
|
+
integer: Sql::TYPES[:integer].meta(native_type: "int(11)"),
|
42
|
+
string: Sql::TYPES[:string].meta(native_type: "varchar(255)"),
|
43
|
+
text: Sql::TYPES[:text].meta(column_type: :string)
|
44
|
+
}.freeze
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,247 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "sequel"
|
4
|
+
|
5
|
+
require "pakyow/support/extension"
|
6
|
+
|
7
|
+
require "pakyow/support/core_refinements/string/normalization"
|
8
|
+
|
9
|
+
require "pakyow/data/adapters/abstract"
|
10
|
+
|
11
|
+
module Pakyow
|
12
|
+
module Data
|
13
|
+
module Adapters
|
14
|
+
class Sql < Abstract
|
15
|
+
require "pakyow/data/adapters/sql/commands"
|
16
|
+
require "pakyow/data/adapters/sql/dataset_methods"
|
17
|
+
require "pakyow/data/adapters/sql/migrator"
|
18
|
+
require "pakyow/data/adapters/sql/runner"
|
19
|
+
require "pakyow/data/adapters/sql/source_extension"
|
20
|
+
|
21
|
+
TYPES = {
|
22
|
+
# overrides for default types
|
23
|
+
boolean: Data::Types::MAPPING[:boolean].meta(default: false, database_type: :boolean, column_type: :boolean),
|
24
|
+
date: Data::Types::MAPPING[:date].meta(database_type: :date),
|
25
|
+
datetime: Data::Types::MAPPING[:datetime].meta(database_type: DateTime),
|
26
|
+
decimal: Data::Types::MAPPING[:decimal].meta(database_type: BigDecimal, size: [10, 2]),
|
27
|
+
float: Data::Types::MAPPING[:float].meta(database_type: :float),
|
28
|
+
integer: Data::Types::MAPPING[:integer].meta(database_type: Integer),
|
29
|
+
string: Data::Types::MAPPING[:string].meta(database_type: String),
|
30
|
+
time: Data::Types::MAPPING[:time].meta(database_type: Time, column_type: :datetime),
|
31
|
+
|
32
|
+
# sql-specific types
|
33
|
+
file: Types.Constructor(Sequel::SQL::Blob).meta(mapping: :file, database_type: File, column_type: :blob),
|
34
|
+
text: Types::Coercible::String.meta(mapping: :text, database_type: :text, column_type: :text, native_type: "text"),
|
35
|
+
bignum: Types::Coercible::Integer.meta(mapping: :bignum, database_type: :Bignum)
|
36
|
+
}.freeze
|
37
|
+
|
38
|
+
require "pakyow/data/adapters/sql/types"
|
39
|
+
|
40
|
+
attr_reader :connection
|
41
|
+
|
42
|
+
DEFAULT_EXTENSIONS = %i(
|
43
|
+
connection_validator
|
44
|
+
).freeze
|
45
|
+
|
46
|
+
DEFAULT_ADAPTER_EXTENSIONS = {
|
47
|
+
postgres: %i(
|
48
|
+
pg_json
|
49
|
+
).freeze
|
50
|
+
}.freeze
|
51
|
+
|
52
|
+
def initialize(opts, logger: nil)
|
53
|
+
@opts, @logger = opts, logger
|
54
|
+
connect
|
55
|
+
end
|
56
|
+
|
57
|
+
def qualify_attribute(attribute, source)
|
58
|
+
Sequel.qualify(source.class.dataset_table, attribute)
|
59
|
+
end
|
60
|
+
|
61
|
+
def alias_attribute(attribute, as)
|
62
|
+
Sequel.as(attribute, as)
|
63
|
+
end
|
64
|
+
|
65
|
+
def dataset_for_source(source)
|
66
|
+
@connection[source.dataset_table]
|
67
|
+
end
|
68
|
+
|
69
|
+
def result_for_attribute_value(attribute, value, source)
|
70
|
+
source.where(attribute => value)
|
71
|
+
end
|
72
|
+
|
73
|
+
def restrict_to_source(restrict_to_source, source, *additional_fields)
|
74
|
+
source.select.qualify(
|
75
|
+
restrict_to_source.class.dataset_table
|
76
|
+
).select_append(
|
77
|
+
*additional_fields
|
78
|
+
)
|
79
|
+
end
|
80
|
+
|
81
|
+
def restrict_to_attribute(attribute, source)
|
82
|
+
source.select(*attribute)
|
83
|
+
end
|
84
|
+
|
85
|
+
def merge_results(left_column_name, right_column_name, mergeable_source, merge_into_source)
|
86
|
+
merge_into_source.tap do
|
87
|
+
merge_into_source.__setobj__(
|
88
|
+
merge_into_source.join(
|
89
|
+
mergeable_source.class.dataset_table, left_column_name => right_column_name
|
90
|
+
)
|
91
|
+
)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def transaction(&block)
|
96
|
+
@connection.transaction do
|
97
|
+
begin
|
98
|
+
block.call
|
99
|
+
rescue Rollback
|
100
|
+
raise Sequel::Rollback
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def connect
|
106
|
+
@connection = Sequel.connect(
|
107
|
+
adapter: @opts[:adapter],
|
108
|
+
database: @opts[:path],
|
109
|
+
host: @opts[:host],
|
110
|
+
port: @opts[:port],
|
111
|
+
user: @opts[:user],
|
112
|
+
password: @opts[:password],
|
113
|
+
logger: @logger
|
114
|
+
)
|
115
|
+
|
116
|
+
(DEFAULT_EXTENSIONS + DEFAULT_ADAPTER_EXTENSIONS[@opts[:adapter].to_s.to_sym].to_a).each do |extension|
|
117
|
+
@connection.extension extension
|
118
|
+
end
|
119
|
+
|
120
|
+
if @opts.include?(:timeout)
|
121
|
+
@connection.pool.connection_validation_timeout = @opts[:timeout].to_i
|
122
|
+
end
|
123
|
+
rescue Sequel::AdapterNotFound => error
|
124
|
+
raise MissingAdapter.build(error)
|
125
|
+
rescue Sequel::DatabaseConnectionError => error
|
126
|
+
raise ConnectionError.build(error)
|
127
|
+
end
|
128
|
+
|
129
|
+
def disconnect
|
130
|
+
if connected?
|
131
|
+
@connection.disconnect
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def connected?
|
136
|
+
@connection.opts[:adapter] == "sqlite" || @connection.test_connection
|
137
|
+
rescue
|
138
|
+
false
|
139
|
+
end
|
140
|
+
|
141
|
+
def migratable?
|
142
|
+
true
|
143
|
+
end
|
144
|
+
|
145
|
+
def auto_migratable?
|
146
|
+
true
|
147
|
+
end
|
148
|
+
|
149
|
+
def finalized_attribute(attribute)
|
150
|
+
if attribute.meta[:primary_key] || attribute.meta[:foreign_key]
|
151
|
+
begin
|
152
|
+
finalized_attribute = Data::Types.type_for(:"pk_#{attribute.meta[:mapping]}", Sql.types_for_adapter(@connection.opts[:adapter].to_sym)).dup
|
153
|
+
|
154
|
+
if attribute.meta[:primary_key]
|
155
|
+
finalized_attribute = finalized_attribute.meta(primary_key: attribute.meta[:primary_key])
|
156
|
+
end
|
157
|
+
|
158
|
+
if attribute.meta[:foreign_key]
|
159
|
+
finalized_attribute = finalized_attribute.meta(foreign_key: attribute.meta[:foreign_key])
|
160
|
+
end
|
161
|
+
rescue Pakyow::UnknownType
|
162
|
+
finalized_attribute = attribute.dup
|
163
|
+
end
|
164
|
+
else
|
165
|
+
finalized_attribute = attribute.dup
|
166
|
+
end
|
167
|
+
|
168
|
+
finalized_meta = finalized_attribute.meta.dup
|
169
|
+
|
170
|
+
if finalized_meta.include?(:mapping)
|
171
|
+
finalized_meta[:migration_type] = finalized_meta[:mapping]
|
172
|
+
end
|
173
|
+
|
174
|
+
unless finalized_meta.include?(:migration_type)
|
175
|
+
finalized_meta[:migration_type] = migration_type_for_attribute(attribute)
|
176
|
+
end
|
177
|
+
|
178
|
+
unless finalized_meta.include?(:column_type)
|
179
|
+
finalized_meta[:column_type] = column_type_for_attribute(attribute)
|
180
|
+
end
|
181
|
+
|
182
|
+
unless finalized_meta.include?(:database_type)
|
183
|
+
finalized_meta[:database_type] = database_type_for_attribute(attribute)
|
184
|
+
end
|
185
|
+
|
186
|
+
finalized_meta.each do |key, value|
|
187
|
+
finalized_meta[key] = case value
|
188
|
+
when Proc
|
189
|
+
if value.arity == 1
|
190
|
+
value.call(finalized_meta)
|
191
|
+
else
|
192
|
+
value.call
|
193
|
+
end
|
194
|
+
else
|
195
|
+
value
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
finalized_attribute.meta(finalized_meta)
|
200
|
+
end
|
201
|
+
|
202
|
+
private
|
203
|
+
|
204
|
+
def migration_type_for_attribute(attribute)
|
205
|
+
attribute.meta[:database_type] || attribute.primitive
|
206
|
+
end
|
207
|
+
|
208
|
+
def column_type_for_attribute(attribute)
|
209
|
+
attribute.primitive.to_s.downcase.to_sym
|
210
|
+
end
|
211
|
+
|
212
|
+
def database_type_for_attribute(attribute)
|
213
|
+
attribute.primitive
|
214
|
+
end
|
215
|
+
|
216
|
+
class << self
|
217
|
+
CONNECTION_TYPES = {
|
218
|
+
postgres: "Types::Postgres",
|
219
|
+
sqlite: "Types::SQLite",
|
220
|
+
mysql2: "Types::MySQL"
|
221
|
+
}.freeze
|
222
|
+
|
223
|
+
def types_for_adapter(adapter)
|
224
|
+
TYPES.dup.merge(const_get(CONNECTION_TYPES[adapter.to_sym])::TYPES)
|
225
|
+
end
|
226
|
+
|
227
|
+
using Support::Refinements::String::Normalization
|
228
|
+
|
229
|
+
def build_opts(opts)
|
230
|
+
database = if opts[:adapter] == "sqlite"
|
231
|
+
if opts[:host]
|
232
|
+
File.join(opts[:host], opts[:path])
|
233
|
+
else
|
234
|
+
opts[:path]
|
235
|
+
end
|
236
|
+
else
|
237
|
+
String.normalize_path(opts[:path])[1..-1]
|
238
|
+
end
|
239
|
+
|
240
|
+
opts[:path] = database
|
241
|
+
opts
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pakyow/support/extension"
|
4
|
+
|
5
|
+
module Pakyow
|
6
|
+
module Data
|
7
|
+
module Behavior
|
8
|
+
module Config
|
9
|
+
extend Support::Extension
|
10
|
+
|
11
|
+
apply_extension do
|
12
|
+
configurable :data do
|
13
|
+
configurable :subscriptions do
|
14
|
+
setting :adapter_settings, {}
|
15
|
+
setting :version
|
16
|
+
|
17
|
+
defaults :production do
|
18
|
+
setting :adapter_settings do
|
19
|
+
{ key_prefix: [Pakyow.config.redis.key_prefix, config.name].join("/") }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pakyow/support/extension"
|
4
|
+
|
5
|
+
require "pakyow/data/container"
|
6
|
+
require "pakyow/data/lookup"
|
7
|
+
require "pakyow/data/subscribers"
|
8
|
+
|
9
|
+
module Pakyow
|
10
|
+
module Data
|
11
|
+
module Behavior
|
12
|
+
module Lookup
|
13
|
+
extend Support::Extension
|
14
|
+
|
15
|
+
# Data container object.
|
16
|
+
#
|
17
|
+
attr_reader :data
|
18
|
+
|
19
|
+
apply_extension do
|
20
|
+
after "boot", "initialize.data", priority: :high do
|
21
|
+
# Validate that each source connection exists.
|
22
|
+
#
|
23
|
+
state(:source).each do |source|
|
24
|
+
Pakyow.connection(source.adapter, source.connection)
|
25
|
+
end
|
26
|
+
|
27
|
+
subscribers = if is_a?(Plugin)
|
28
|
+
# Plugins should use the same subscribers object as their parent app.
|
29
|
+
#
|
30
|
+
parent.data.subscribers
|
31
|
+
else
|
32
|
+
Subscribers.new(
|
33
|
+
self,
|
34
|
+
Pakyow.config.data.subscriptions.adapter,
|
35
|
+
Pakyow.config.data.subscriptions.adapter_settings
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
containers = Pakyow.data_connections.values.each_with_object([]) { |connections, arr|
|
40
|
+
connections.values.each do |connection|
|
41
|
+
arr << Container.new(
|
42
|
+
connection: connection,
|
43
|
+
sources: state(:source).select { |source|
|
44
|
+
connection.name == source.connection && connection.type == source.adapter
|
45
|
+
},
|
46
|
+
objects: state(:object)
|
47
|
+
)
|
48
|
+
end
|
49
|
+
}
|
50
|
+
|
51
|
+
containers.each do |container|
|
52
|
+
container.finalize_associations!(containers - [container])
|
53
|
+
end
|
54
|
+
|
55
|
+
containers.each do |container|
|
56
|
+
container.finalize_sources!(containers - [container])
|
57
|
+
end
|
58
|
+
|
59
|
+
@data = Data::Lookup.new(
|
60
|
+
app: self,
|
61
|
+
containers: containers,
|
62
|
+
subscribers: subscribers
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
66
|
+
on "shutdown" do
|
67
|
+
if instance_variable_defined?(:@data)
|
68
|
+
@data.subscribers.shutdown
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pakyow/support/extension"
|
4
|
+
require "pakyow/support/serializer"
|
5
|
+
|
6
|
+
module Pakyow
|
7
|
+
module Data
|
8
|
+
module Behavior
|
9
|
+
# Persists in-memory subscribers across restarts.
|
10
|
+
#
|
11
|
+
module Serialization
|
12
|
+
extend Support::Extension
|
13
|
+
|
14
|
+
apply_extension do
|
15
|
+
on "shutdown", priority: :high do
|
16
|
+
if Pakyow.config.data.subscriptions.adapter == :memory && data
|
17
|
+
subscriber_serializer.serialize
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
after "boot" do
|
22
|
+
if Pakyow.config.data.subscriptions.adapter == :memory && data
|
23
|
+
subscriber_serializer.deserialize
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private def subscriber_serializer
|
29
|
+
Support::Serializer.new(
|
30
|
+
data.subscribers.adapter,
|
31
|
+
name: "#{config.name}-subscribers",
|
32
|
+
path: File.join(
|
33
|
+
Pakyow.config.root, "tmp", "state"
|
34
|
+
)
|
35
|
+
)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "uri"
|
4
|
+
require "forwardable"
|
5
|
+
|
6
|
+
require "pakyow/support/class_state"
|
7
|
+
require "pakyow/support/deep_freeze"
|
8
|
+
require "pakyow/support/indifferentize"
|
9
|
+
require "pakyow/support/inflector"
|
10
|
+
|
11
|
+
module Pakyow
|
12
|
+
module Data
|
13
|
+
class Connection
|
14
|
+
extend Forwardable
|
15
|
+
def_delegators :@adapter, :dataset_for_source, :transaction
|
16
|
+
|
17
|
+
attr_reader :type, :name, :opts, :adapter, :failure
|
18
|
+
|
19
|
+
extend Support::DeepFreeze
|
20
|
+
unfreezable :logger, :adapter
|
21
|
+
|
22
|
+
def initialize(type:, name:, string: nil, opts: nil, logger: nil)
|
23
|
+
@type, @name, @logger, @failure = type, name, logger, nil
|
24
|
+
|
25
|
+
@opts = self.class.adapter(type).build_opts(
|
26
|
+
opts.is_a?(Hash) ? opts : self.class.parse_connection_string(string)
|
27
|
+
)
|
28
|
+
|
29
|
+
@adapter = self.class.adapter(@type).new(@opts, logger: @logger)
|
30
|
+
rescue ConnectionError, MissingAdapter => error
|
31
|
+
error.context = self
|
32
|
+
@failure = error
|
33
|
+
end
|
34
|
+
|
35
|
+
def connected?
|
36
|
+
!failed? && @adapter.connected?
|
37
|
+
end
|
38
|
+
|
39
|
+
def failed?
|
40
|
+
!@failure.nil?
|
41
|
+
end
|
42
|
+
|
43
|
+
def auto_migrate?
|
44
|
+
migratable? && @adapter.auto_migratable?
|
45
|
+
end
|
46
|
+
|
47
|
+
def migratable?
|
48
|
+
connected? && @adapter.migratable?
|
49
|
+
end
|
50
|
+
|
51
|
+
def connect
|
52
|
+
@adapter.connect
|
53
|
+
end
|
54
|
+
|
55
|
+
def disconnect
|
56
|
+
@adapter.disconnect
|
57
|
+
end
|
58
|
+
|
59
|
+
def types
|
60
|
+
if @adapter.class.const_defined?("TYPES")
|
61
|
+
@adapter.class.types_for_adapter(adapter.connection.opts[:adapter])
|
62
|
+
else
|
63
|
+
{}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
extend Support::ClassState
|
68
|
+
class_state :adapter_types, default: []
|
69
|
+
|
70
|
+
using Support::Indifferentize
|
71
|
+
|
72
|
+
class << self
|
73
|
+
def parse_connection_string(connection_string)
|
74
|
+
uri = URI(connection_string)
|
75
|
+
|
76
|
+
{
|
77
|
+
adapter: uri.scheme,
|
78
|
+
path: uri.path,
|
79
|
+
host: uri.host,
|
80
|
+
port: uri.port,
|
81
|
+
user: uri.user,
|
82
|
+
password: uri.password
|
83
|
+
}.merge(
|
84
|
+
CGI::parse(uri.query.to_s).transform_values(&:first).indifferentize
|
85
|
+
)
|
86
|
+
end
|
87
|
+
|
88
|
+
def register_adapter(type)
|
89
|
+
(@adapter_types << type).uniq!
|
90
|
+
end
|
91
|
+
|
92
|
+
def adapter(type)
|
93
|
+
if @adapter_types.include?(type.to_sym)
|
94
|
+
require "pakyow/data/adapters/#{type}"
|
95
|
+
Adapters.const_get(Support.inflector.camelize(type))
|
96
|
+
else
|
97
|
+
raise UnknownAdapter.new_with_message(type: type)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|