pakyow-data 1.0.0.rc1
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 +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
|