litestack 0.4.3 → 0.4.4
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/.standard.yml +3 -1
- data/BENCHMARKS.md +3 -3
- data/CAVEATS.md +20 -0
- data/CHANGELOG.md +19 -4
- data/FILESYSTEMS.md +55 -0
- data/Gemfile +2 -0
- data/README.md +2 -2
- data/ROADMAP.md +6 -6
- data/assets/litestack_advantage.png +0 -0
- data/bench/bench.rb +2 -0
- data/bench/bench_cache_rails.rb +14 -17
- data/bench/bench_cache_raw.rb +18 -15
- data/bench/bench_jobs_rails.rb +3 -3
- data/bench/bench_jobs_raw.rb +3 -3
- data/bin/liteboard +16 -14
- data/gemfiles/rails70.gemfile +5 -0
- data/gemfiles/rails71.gemfile +5 -0
- data/gemfiles/rails71.gemfile.lock +264 -0
- data/lib/active_job/queue_adapters/litejob_adapter.rb +11 -3
- data/lib/active_record/connection_adapters/litedb_adapter.rb +17 -7
- data/lib/active_support/cache/litecache.rb +25 -15
- data/lib/generators/litestack/install/install_generator.rb +2 -2
- data/lib/litestack/liteboard/liteboard.rb +15 -19
- data/lib/litestack/liteboard/views/litecable.erb +1 -1
- data/lib/litestack/litecable.rb +1 -1
- data/lib/litestack/litecache.rb +23 -25
- data/lib/litestack/litedb.rb +5 -1
- data/lib/litestack/litejob.rb +1 -1
- data/lib/litestack/litejobqueue.rb +24 -14
- data/lib/litestack/litemetric.rb +7 -6
- data/lib/litestack/litequeue.rb +17 -2
- data/lib/litestack/litequeue.sql.yml +38 -5
- data/lib/litestack/litescheduler.rb +1 -2
- data/lib/litestack/litesearch/index.rb +11 -10
- data/lib/litestack/litesearch/model.rb +61 -3
- data/lib/litestack/litesearch/schema.rb +7 -2
- data/lib/litestack/litesearch/schema_adapters/backed_adapter.rb +69 -25
- data/lib/litestack/litesearch/schema_adapters.rb +4 -4
- data/lib/litestack/litesearch.rb +2 -2
- data/lib/litestack/litesupport.rb +7 -6
- data/lib/litestack/railtie.rb +4 -2
- data/lib/litestack/version.rb +1 -1
- data/lib/litestack.rb +15 -15
- data/lib/sequel/adapters/litedb.rb +9 -1
- data/lib/sequel/adapters/shared/litedb.rb +2 -2
- data/scripts/build_metrics.rb +2 -2
- data/scripts/test_cable.rb +1 -1
- metadata +95 -59
data/lib/litestack/litejob.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_stringe_literal: true
|
2
2
|
|
3
|
-
require_relative "
|
3
|
+
require_relative "litejobqueue"
|
4
4
|
|
5
5
|
##
|
6
6
|
# Litejob is a Ruby module that enables seamless integration of the Litejobqueue job queueing system into Ruby applications. By including the Litejob module in a class and implementing the #perform method, developers can easily enqueue and process jobs asynchronously.
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_stringe_literal: true
|
2
2
|
|
3
|
-
require_relative "
|
4
|
-
require_relative "
|
3
|
+
require_relative "litequeue"
|
4
|
+
require_relative "litemetric"
|
5
5
|
|
6
6
|
##
|
7
7
|
# Litejobqueue is a job queueing and processing system designed for Ruby applications. It is built on top of SQLite, which is an embedded relational database management system that is #lightweight and fast.
|
@@ -15,7 +15,7 @@ class Litejobqueue < Litequeue
|
|
15
15
|
include Litemetric::Measurable
|
16
16
|
|
17
17
|
# the default options for the job queue
|
18
|
-
# can be
|
18
|
+
# can be overridden by passing new options in a hash
|
19
19
|
# to Litejobqueue.new, it will also be then passed to the underlying Litequeue object
|
20
20
|
# config_path: "./litejob.yml" -> were to find the configuration file (if any)
|
21
21
|
# path: "./db/queue.db"
|
@@ -136,20 +136,28 @@ class Litejobqueue < Litequeue
|
|
136
136
|
# @@queue = nil
|
137
137
|
close
|
138
138
|
end
|
139
|
-
|
139
|
+
|
140
140
|
private
|
141
|
+
|
142
|
+
def prepare_search_options(opts)
|
143
|
+
sql_opts = super(opts)
|
144
|
+
sql_opts[:klass] = opts[:klass]
|
145
|
+
sql_opts[:params] = opts[:params]
|
146
|
+
sql_opts
|
147
|
+
end
|
141
148
|
|
142
149
|
def exit_callback
|
143
150
|
@running = false # stop all workers
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
+
if @jobs_in_flight > 0
|
152
|
+
puts "--- Litejob detected an exit, cleaning up"
|
153
|
+
index = 0
|
154
|
+
while @jobs_in_flight > 0 && index < 30 # 3 seconds grace period for jobs to finish
|
155
|
+
puts "--- Waiting for #{@jobs_in_flight} jobs to finish"
|
156
|
+
sleep 0.1
|
157
|
+
index += 1
|
158
|
+
end
|
159
|
+
puts " --- Exiting with #{@jobs_in_flight} jobs in flight"
|
151
160
|
end
|
152
|
-
puts " --- Exiting with #{@jobs_in_flight} jobs in flight"
|
153
161
|
end
|
154
162
|
|
155
163
|
def setup
|
@@ -179,6 +187,8 @@ class Litejobqueue < Litequeue
|
|
179
187
|
|
180
188
|
# create a worker according to environment
|
181
189
|
def create_worker
|
190
|
+
# temporarily stop this feature until a better solution is implemented
|
191
|
+
#return if defined?(Rails) && !defined?(Rails::Server)
|
182
192
|
Litescheduler.spawn do
|
183
193
|
worker_sleep_index = 0
|
184
194
|
while @running
|
@@ -186,7 +196,7 @@ class Litejobqueue < Litequeue
|
|
186
196
|
@queues.each do |priority, queues| # iterate through the levels
|
187
197
|
queues.each do |queue, spawns| # iterate through the queues in the level
|
188
198
|
batched = 0
|
189
|
-
|
199
|
+
|
190
200
|
while (batched < priority) && (payload = pop(queue, 1)) # fearlessly use the same queue object
|
191
201
|
capture(:dequeue, queue)
|
192
202
|
processed += 1
|
@@ -201,7 +211,7 @@ class Litejobqueue < Litequeue
|
|
201
211
|
end
|
202
212
|
if processed == 0
|
203
213
|
sleep @options[:sleep_intervals][worker_sleep_index]
|
204
|
-
worker_sleep_index += 1 if worker_sleep_index < @options[:sleep_intervals].length - 1
|
214
|
+
worker_sleep_index += 1 if worker_sleep_index < (@options[:sleep_intervals].length - 1)
|
205
215
|
else
|
206
216
|
worker_sleep_index = 0 # reset the index
|
207
217
|
end
|
data/lib/litestack/litemetric.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require "singleton"
|
4
4
|
|
5
|
-
require_relative "
|
5
|
+
require_relative "litesupport"
|
6
6
|
|
7
7
|
# this class is a singleton
|
8
8
|
# and should remain so
|
@@ -148,10 +148,11 @@ class Litemetric
|
|
148
148
|
end
|
149
149
|
|
150
150
|
def exit_callback
|
151
|
-
return unless @collector.count > 0
|
152
|
-
warn "--- Litemetric detected an exit, flushing metrics"
|
153
151
|
@running = false
|
154
|
-
@collector.
|
152
|
+
if @collector.count > 0
|
153
|
+
warn "--- Litemetric detected an exit, flushing metrics"
|
154
|
+
@collector.flush
|
155
|
+
end
|
155
156
|
end
|
156
157
|
|
157
158
|
def setup
|
@@ -203,8 +204,8 @@ class Litemetric
|
|
203
204
|
def create_snapshotter
|
204
205
|
Litescheduler.spawn do
|
205
206
|
while @running
|
206
|
-
sleep @litemetric.options[:snapshot_interval]
|
207
207
|
capture_snapshot
|
208
|
+
sleep @litemetric.options[:snapshot_interval]
|
208
209
|
end
|
209
210
|
end
|
210
211
|
end
|
@@ -275,7 +276,7 @@ class Litemetric
|
|
275
276
|
def capture_single_key(topic, event, key, value, time = nil)
|
276
277
|
run_stmt(:capture_event, topic.to_s, event.to_s, key.to_s, time, 1, value)
|
277
278
|
end
|
278
|
-
|
279
|
+
|
279
280
|
def count
|
280
281
|
run_stmt(:event_count)[0][0]
|
281
282
|
end
|
data/lib/litestack/litequeue.rb
CHANGED
@@ -13,7 +13,7 @@ require_relative "litesupport"
|
|
13
13
|
|
14
14
|
class Litequeue
|
15
15
|
# the default options for the queue
|
16
|
-
# can be
|
16
|
+
# can be overridden by passing new options in a hash
|
17
17
|
# to Litequeue.new
|
18
18
|
# path: "./queue.db"
|
19
19
|
# mmap_size: 128 * 1024 * 1024 -> 128MB to be held in memory
|
@@ -80,7 +80,7 @@ class Litequeue
|
|
80
80
|
run_sql("DELETE FROM queue WHERE iif(?1 IS NOT NULL, name = ?1, TRUE)", queue)
|
81
81
|
end
|
82
82
|
|
83
|
-
# returns a count of entries in all queues, or if a queue name is given,
|
83
|
+
# returns a count of entries in all queues, or if a queue name is given, returns the count of entries in that queue
|
84
84
|
def count(queue = nil)
|
85
85
|
run_sql("SELECT count(*) FROM queue WHERE iif(?1 IS NOT NULL, name = ?1, TRUE)", queue)[0][0]
|
86
86
|
end
|
@@ -111,9 +111,24 @@ class Litequeue
|
|
111
111
|
queues: queues
|
112
112
|
}
|
113
113
|
end
|
114
|
+
|
115
|
+
def find(opts = {})
|
116
|
+
run_stmt(:search, prepare_search_options(opts))
|
117
|
+
end
|
114
118
|
|
115
119
|
private
|
116
120
|
|
121
|
+
def prepare_search_options(opts)
|
122
|
+
sql_opts = {}
|
123
|
+
sql_opts[:fire_at_from] = opts[:fire_at][0] rescue nil
|
124
|
+
sql_opts[:fire_at_to] = opts[:fire_at][1] rescue nil
|
125
|
+
sql_opts[:created_at_from] = opts[:created_at][0] rescue nil
|
126
|
+
sql_opts[:created_at_to] = opts[:created_at][1] rescue nil
|
127
|
+
sql_opts[:name] = opts[:queue]
|
128
|
+
sql_opts[:dir] = opts[:dir] == :desc ? -1 : 1
|
129
|
+
sql_opts
|
130
|
+
end
|
131
|
+
|
117
132
|
def create_connection
|
118
133
|
super("#{__dir__}/litequeue.sql.yml") do |conn|
|
119
134
|
conn.wal_autocheckpoint = 10000
|
@@ -4,7 +4,7 @@ schema:
|
|
4
4
|
CREATE TABLE IF NOT EXISTS queue(
|
5
5
|
id TEXT PRIMARY KEY DEFAULT(hex(randomblob(32))) NOT NULL ON CONFLICT REPLACE,
|
6
6
|
name TEXT DEFAULT('default') NOT NULL ON CONFLICT REPLACE,
|
7
|
-
fire_at
|
7
|
+
fire_at REAL DEFAULT(unixepoch('subsec')) NOT NULL ON CONFLICT REPLACE,
|
8
8
|
value TEXT,
|
9
9
|
created_at INTEGER DEFAULT(unixepoch()) NOT NULL ON CONFLICT REPLACE
|
10
10
|
) WITHOUT ROWID;
|
@@ -16,21 +16,36 @@ stmts:
|
|
16
16
|
|
17
17
|
push: >
|
18
18
|
INSERT INTO queue(id, name, fire_at, value)
|
19
|
-
VALUES (hex(randomblob(32)), $1, (unixepoch() + $2), $3)
|
19
|
+
VALUES (hex(randomblob(32)), $1, (unixepoch('subsec') + $2), $3)
|
20
20
|
RETURNING id, name;
|
21
21
|
|
22
22
|
repush: >
|
23
23
|
INSERT INTO queue(id, name, fire_at, value)
|
24
|
-
VALUES (?, ?, (unixepoch() + ?), ?)
|
24
|
+
VALUES (?, ?, (unixepoch('subsec') + ?), ?)
|
25
25
|
RETURNING name;
|
26
26
|
|
27
|
+
pop_in_place: >
|
28
|
+
UPDATE queue
|
29
|
+
SET
|
30
|
+
name = '->:' || name,
|
31
|
+
fire_at = unixepoch('subsec')
|
32
|
+
WHERE (name, fire_at, id)
|
33
|
+
IN (
|
34
|
+
SELECT name, fire_at, id FROM queue
|
35
|
+
WHERE name = ifnull($1, 'default')
|
36
|
+
AND fire_at <= (unixepoch('subsec'))
|
37
|
+
ORDER BY fire_at ASC
|
38
|
+
LIMIT ifnull($2, 1)
|
39
|
+
)
|
40
|
+
RETURNING id, value;
|
41
|
+
|
27
42
|
pop: >
|
28
43
|
DELETE FROM queue
|
29
44
|
WHERE (name, fire_at, id)
|
30
45
|
IN (
|
31
46
|
SELECT name, fire_at, id FROM queue
|
32
47
|
WHERE name = ifnull($1, 'default')
|
33
|
-
AND fire_at <= (unixepoch())
|
48
|
+
AND fire_at <= (unixepoch('subsec'))
|
34
49
|
ORDER BY fire_at ASC
|
35
50
|
LIMIT ifnull($2, 1)
|
36
51
|
)
|
@@ -40,7 +55,25 @@ stmts:
|
|
40
55
|
DELETE FROM queue
|
41
56
|
WHERE id = $1
|
42
57
|
RETURNING value;
|
43
|
-
|
58
|
+
|
59
|
+
search: >
|
60
|
+
SELECT *, :params, value ->> '$.params', value ->> '$.params' LIKE '%'||'three_second'||'%' FROM queue
|
61
|
+
WHERE
|
62
|
+
name = ifnull(:name, name)
|
63
|
+
AND
|
64
|
+
iif(:fire_at_from, fire_at >= :fire_at_from, true)
|
65
|
+
AND
|
66
|
+
iif(:fire_at_to, fire_at <= :fire_at_to, true)
|
67
|
+
AND
|
68
|
+
iif(:created_at_from, created_at >= :created_at_from, true)
|
69
|
+
AND
|
70
|
+
iif(:created_at_to, created_at <= :created_at_to, true)
|
71
|
+
AND
|
72
|
+
iif(:klass IS NOT NULL, value ->> '$.klass' LIKE '%'||:klass||'%', true)
|
73
|
+
AND
|
74
|
+
iif(:params IS NOT NULL, value ->> '$.params' LIKE '%'||:params||'%', true)
|
75
|
+
ORDER BY created_at * :dir;
|
76
|
+
|
44
77
|
info: >
|
45
78
|
SELECT
|
46
79
|
name,
|
@@ -1,5 +1,5 @@
|
|
1
1
|
require "oj"
|
2
|
-
require_relative "
|
2
|
+
require_relative "schema"
|
3
3
|
|
4
4
|
class Litesearch::Index
|
5
5
|
DEFAULT_SEARCH_OPTIONS = {limit: 25, offset: 0}
|
@@ -71,7 +71,7 @@ class Litesearch::Index
|
|
71
71
|
|
72
72
|
def rebuild!
|
73
73
|
if @db.transaction_active?
|
74
|
-
|
74
|
+
do_rebuild
|
75
75
|
else
|
76
76
|
@db.transaction(:immediate) { do_rebuild }
|
77
77
|
end
|
@@ -102,19 +102,20 @@ class Litesearch::Index
|
|
102
102
|
rs = @stmts[:search].execute(term, options[:limit], options[:offset])
|
103
103
|
generate_results(rs)
|
104
104
|
end
|
105
|
-
|
106
|
-
def similar(id, limit=10)
|
105
|
+
|
106
|
+
def similar(id, limit = 10)
|
107
107
|
# pp term = @db.execute(@schema.sql_for(:similarity_query), id)
|
108
|
-
if @schema.schema[:tokenizer] == :trigram
|
108
|
+
rs = if @schema.schema[:tokenizer] == :trigram
|
109
109
|
# just use the normal similarity approach for now
|
110
110
|
# need to recondisder that for trigram indexes later
|
111
|
-
|
111
|
+
@stmts[:similar].execute(id, limit) # standard:disable Style/IdenticalConditionalBranches
|
112
112
|
else
|
113
|
-
|
113
|
+
@stmts[:similar].execute(id, limit) # standard:disable Style/IdenticalConditionalBranches
|
114
114
|
end
|
115
|
+
|
115
116
|
generate_results(rs)
|
116
117
|
end
|
117
|
-
|
118
|
+
|
118
119
|
def clear!
|
119
120
|
@stmts[:delete_all].execute!(id)
|
120
121
|
end
|
@@ -140,7 +141,7 @@ class Litesearch::Index
|
|
140
141
|
else
|
141
142
|
result = rs.to_a
|
142
143
|
end
|
143
|
-
result
|
144
|
+
result
|
144
145
|
end
|
145
146
|
|
146
147
|
def exists?(name)
|
@@ -175,7 +176,7 @@ class Litesearch::Index
|
|
175
176
|
|
176
177
|
def do_modify(new_schema)
|
177
178
|
changes = @schema.compare(new_schema)
|
178
|
-
# ensure the new schema maintains
|
179
|
+
# ensure the new schema maintains field order
|
179
180
|
new_schema.order_fields(@schema)
|
180
181
|
# with the changes object decide what needs to be done to the schema
|
181
182
|
requires_schema_change = false
|
@@ -11,6 +11,7 @@ module Litesearch::Model
|
|
11
11
|
klass.include Litesearch::Model::ActiveRecordInstanceMethods
|
12
12
|
klass.extend Litesearch::Model::ActiveRecordClassMethods
|
13
13
|
ActiveRecord::Base.extend Litesearch::Model::BaseClassMethods
|
14
|
+
Litesearch::Schema.prepend Litesearch::Model::ActiveRecordSchemaMethods
|
14
15
|
end
|
15
16
|
end
|
16
17
|
|
@@ -21,7 +22,7 @@ module Litesearch::Model
|
|
21
22
|
end
|
22
23
|
|
23
24
|
module InstanceMethods
|
24
|
-
def similar(limit=10)
|
25
|
+
def similar(limit = 10)
|
25
26
|
conn = self.class.get_connection
|
26
27
|
idx = conn.search_index(self.class.send(:index_name))
|
27
28
|
r_a_h = conn.results_as_hash
|
@@ -36,14 +37,38 @@ module Litesearch::Model
|
|
36
37
|
end
|
37
38
|
result
|
38
39
|
end
|
39
|
-
|
40
40
|
end
|
41
41
|
|
42
42
|
module ClassMethods
|
43
|
+
|
43
44
|
def litesearch
|
45
|
+
# it is possible that this code is running when there is no table created yet
|
46
|
+
if !defined?(ActiveRecord::Base).nil? && ancestors.include?(ActiveRecord::Base)
|
47
|
+
unless table_exists?
|
48
|
+
# capture the schema block
|
49
|
+
@schema = ::Litesearch::Schema.new
|
50
|
+
@schema.model_class = self if @schema.respond_to? :model_class
|
51
|
+
@schema.type :backed
|
52
|
+
@schema.table table_name.to_sym
|
53
|
+
yield @schema
|
54
|
+
@schema.post_init
|
55
|
+
@schema_not_created = true
|
56
|
+
after_initialize do
|
57
|
+
if self.class.instance_variable_get(:@schema_not_created)
|
58
|
+
self.class.get_connection.search_index(self.class.index_name) do |schema|
|
59
|
+
@schema.model_class = self.class if @schema.respond_to? :model_class
|
60
|
+
schema.merge(self.class.instance_variable_get(:@schema))
|
61
|
+
end
|
62
|
+
self.class.instance_variable_set(:@schema_not_created, false)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
return nil
|
66
|
+
end
|
67
|
+
end
|
44
68
|
idx = get_connection.search_index(index_name) do |schema|
|
45
69
|
schema.type :backed
|
46
70
|
schema.table table_name.to_sym
|
71
|
+
schema.model_class = self if schema.respond_to? :model_class
|
47
72
|
yield schema
|
48
73
|
schema.post_init
|
49
74
|
@schema = schema # save the schema
|
@@ -77,7 +102,7 @@ module Litesearch::Model
|
|
77
102
|
else
|
78
103
|
models_hash = search_models
|
79
104
|
end
|
80
|
-
# remove the models from the options hash before passing it
|
105
|
+
# remove the models from the options hash before passing it to the query
|
81
106
|
options.delete(:models)
|
82
107
|
models_hash.each do |name, klass|
|
83
108
|
selects << "SELECT '#{name}' AS model, rowid, -rank AS search_rank FROM #{index_name_for_table(klass.table_name)}(:term)"
|
@@ -109,6 +134,32 @@ module Litesearch::Model
|
|
109
134
|
end
|
110
135
|
end
|
111
136
|
|
137
|
+
module ActiveRecordSchemaMethods
|
138
|
+
|
139
|
+
attr_accessor :model_class
|
140
|
+
|
141
|
+
def field(name, attributes = {})
|
142
|
+
keys = attributes.keys
|
143
|
+
if keys.include?(:action_text) || keys.include?(:rich_text)
|
144
|
+
attributes[:source] = "#{ActionText::RichText.table_name}.body" rescue "action_text_rich_texts.body"
|
145
|
+
attributes[:reference] = :record_id
|
146
|
+
attributes[:conditions] = { record_type: model_class.name }
|
147
|
+
attributes[:target] = nil
|
148
|
+
elsif keys.include? :as
|
149
|
+
attributes[:source] = attributes[:target] unless attributes[:source]
|
150
|
+
attributes[:reference] = "#{attributes[:as]}_id"
|
151
|
+
attributes[:conditions] = {"#{attributes[:as]}_type".to_sym => model_class.name }
|
152
|
+
attributes[:target] = nil
|
153
|
+
end
|
154
|
+
super(name, attributes)
|
155
|
+
end
|
156
|
+
|
157
|
+
def allowed_attributes
|
158
|
+
super + [:polymorphic, :as, :action_text]
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
|
112
163
|
module ActiveRecordInstanceMethods; end
|
113
164
|
|
114
165
|
module ActiveRecordClassMethods
|
@@ -121,6 +172,13 @@ module Litesearch::Model
|
|
121
172
|
end
|
122
173
|
|
123
174
|
def search(term)
|
175
|
+
if @schema_not_created
|
176
|
+
get_connection.search_index(index_name) do |schema|
|
177
|
+
schema.merge(@schema)
|
178
|
+
schema.model_class = self if schema.respond_to? :model_class
|
179
|
+
end
|
180
|
+
@schema_not_created = false
|
181
|
+
end
|
124
182
|
self.select(
|
125
183
|
"#{table_name}.*"
|
126
184
|
).joins(
|
@@ -1,6 +1,7 @@
|
|
1
|
-
require_relative "
|
1
|
+
require_relative "schema_adapters"
|
2
2
|
|
3
3
|
class Litesearch::Schema
|
4
|
+
|
4
5
|
TOKENIZERS = {
|
5
6
|
porter: "porter unicode61 remove_diacritics 2",
|
6
7
|
unicode: "unicode61 remove_diacritics 2",
|
@@ -34,6 +35,10 @@ class Litesearch::Schema
|
|
34
35
|
@schema[:fields] = {} unless @schema[:fields]
|
35
36
|
end
|
36
37
|
|
38
|
+
def merge(other_schema)
|
39
|
+
@schema.merge!(other_schema.schema)
|
40
|
+
end
|
41
|
+
|
37
42
|
# schema definition API
|
38
43
|
def name(new_name)
|
39
44
|
@schema[:name] = new_name
|
@@ -181,7 +186,7 @@ class Litesearch::Schema
|
|
181
186
|
end
|
182
187
|
|
183
188
|
def allowed_attributes
|
184
|
-
[:weight, :col, :target]
|
189
|
+
[:weight, :col, :target, :source, :conditions, :reference]
|
185
190
|
end
|
186
191
|
end
|
187
192
|
|
@@ -25,20 +25,24 @@ class Litesearch::Schema::BackedAdapter < Litesearch::Schema::ContentlessAdapter
|
|
25
25
|
|
26
26
|
def create_primary_triggers_sql(active = false)
|
27
27
|
when_stmt = "TRUE"
|
28
|
-
cols = active_cols_names
|
28
|
+
cols = active_cols_names.select{|n| !n.nil?}
|
29
29
|
if (filter = @schema[:filter_column])
|
30
30
|
when_stmt = "NEW.#{filter} = TRUE"
|
31
31
|
cols << filter
|
32
32
|
end
|
33
|
+
update_filter = ""
|
34
|
+
if cols.length > 0
|
35
|
+
" OF #{cols.join(', ')} "
|
36
|
+
end
|
33
37
|
|
34
38
|
<<-SQL
|
35
39
|
CREATE TRIGGER #{name}_insert AFTER INSERT ON #{table} WHEN #{when_stmt} BEGIN
|
36
40
|
INSERT OR REPLACE INTO #{name}(rowid, #{active_field_names.join(", ")}) VALUES (NEW.rowid, #{trigger_cols_sql});
|
37
41
|
END;
|
38
|
-
CREATE TRIGGER #{name}_update AFTER UPDATE
|
42
|
+
CREATE TRIGGER #{name}_update AFTER UPDATE #{update_filter} ON #{table} WHEN #{when_stmt} BEGIN
|
39
43
|
INSERT OR REPLACE INTO #{name}(rowid, #{active_field_names.join(", ")}) VALUES (NEW.rowid, #{trigger_cols_sql});
|
40
44
|
END;
|
41
|
-
CREATE TRIGGER #{name}_update_not AFTER UPDATE
|
45
|
+
CREATE TRIGGER #{name}_update_not AFTER UPDATE #{update_filter} ON #{table} WHEN NOT #{when_stmt} BEGIN
|
42
46
|
DELETE FROM #{name} WHERE rowid = NEW.rowid;
|
43
47
|
END;
|
44
48
|
CREATE TRIGGER #{name}_delete AFTER DELETE ON #{table} BEGIN
|
@@ -51,19 +55,41 @@ class Litesearch::Schema::BackedAdapter < Litesearch::Schema::ContentlessAdapter
|
|
51
55
|
"DROP TRIGGER IF EXISTS #{target_table}_#{target_col}_#{col}_#{name}_update;"
|
52
56
|
end
|
53
57
|
|
58
|
+
def drop_secondary_trigger_poly_sql(target_table, target_col, col)
|
59
|
+
"DROP TRIGGER IF EXISTS #{target_table}_#{target_col}_#{name}_update;"
|
60
|
+
end
|
61
|
+
|
54
62
|
def create_secondary_trigger_sql(target_table, target_col, col)
|
55
63
|
<<~SQL
|
56
|
-
CREATE TRIGGER #{target_table}_#{target_col}_#{col}_#{name}_update AFTER UPDATE OF #{target_col} ON #{target_table} BEGIN
|
64
|
+
CREATE TRIGGER IF NOT EXISTS #{target_table}_#{target_col}_#{col}_#{name}_update AFTER UPDATE OF #{target_col} ON #{target_table} BEGIN
|
57
65
|
#{rebuild_sql} AND #{table}.#{col} = NEW.id;
|
58
66
|
END;
|
59
67
|
SQL
|
60
68
|
end
|
61
69
|
|
70
|
+
def create_secondary_trigger_poly_sql(target_table, target_col, col, conditions)
|
71
|
+
conditions_sql = conditions.collect{|k, v| "NEW.#{k} = '#{v}'"}.join(" AND ")
|
72
|
+
<<~SQL
|
73
|
+
CREATE TRIGGER IF NOT EXISTS #{target_table}_#{target_col}_#{name}_insert AFTER INSERT ON #{target_table} WHEN #{conditions_sql} BEGIN
|
74
|
+
#{rebuild_sql};
|
75
|
+
END;
|
76
|
+
CREATE TRIGGER IF NOT EXISTS #{target_table}_#{target_col}_#{name}_update AFTER UPDATE ON #{target_table} WHEN #{conditions_sql} BEGIN
|
77
|
+
#{rebuild_sql};
|
78
|
+
END;
|
79
|
+
SQL
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
|
62
84
|
def drop_secondary_triggers_sql
|
63
85
|
sql = ""
|
64
86
|
@schema[:fields].each do |name, field|
|
65
87
|
if field[:trigger_sql]
|
66
|
-
|
88
|
+
if field[:col]
|
89
|
+
sql << drop_secondary_trigger_sql(field[:target_table], field[:target_col], field[:col])
|
90
|
+
elsif field[:source]
|
91
|
+
sql << drop_secondary_trigger_poly_sql(field[:target_table], field[:target_col], name)
|
92
|
+
end
|
67
93
|
end
|
68
94
|
end
|
69
95
|
sql.empty? ? nil : sql
|
@@ -73,18 +99,18 @@ class Litesearch::Schema::BackedAdapter < Litesearch::Schema::ContentlessAdapter
|
|
73
99
|
sql = ""
|
74
100
|
@schema[:fields].each do |name, field|
|
75
101
|
if field[:trigger_sql]
|
76
|
-
|
102
|
+
if field[:col]
|
103
|
+
sql << create_secondary_trigger_sql(field[:target_table], field[:target_col], field[:col])
|
104
|
+
elsif field[:source]
|
105
|
+
sql << create_secondary_trigger_poly_sql(field[:target_table], field[:target_col], name, field[:conditions])
|
106
|
+
end
|
77
107
|
end
|
78
108
|
end
|
79
109
|
sql.empty? ? nil : sql
|
80
110
|
end
|
81
111
|
|
82
112
|
def rebuild_sql
|
83
|
-
|
84
|
-
jcs = join_conditions_sql
|
85
|
-
fs = filter_sql
|
86
|
-
conditions = " ON #{jcs} #{fs}" unless jcs.empty? && fs.empty?
|
87
|
-
"INSERT OR REPLACE INTO #{name}(rowid, #{active_field_names.join(", ")}) SELECT #{table}.id, #{select_cols_sql} FROM #{join_tables_sql} #{conditions}"
|
113
|
+
"INSERT OR REPLACE INTO #{name}(rowid, #{active_field_names.join(", ")}) SELECT #{table}.id, #{select_cols_sql} FROM #{joins_sql} #{filter_sql}"
|
88
114
|
end
|
89
115
|
|
90
116
|
def enrich_schema
|
@@ -92,12 +118,23 @@ class Litesearch::Schema::BackedAdapter < Litesearch::Schema::ContentlessAdapter
|
|
92
118
|
if field[:target] && !field[:target].start_with?("#{table}.")
|
93
119
|
field[:target] = field[:target].downcase
|
94
120
|
target_table, target_col = field[:target].split(".")
|
95
|
-
field[:col] = "#{name}_id"
|
121
|
+
field[:col] = :"#{name}_id" unless field[:col]
|
96
122
|
field[:target_table] = target_table.to_sym
|
97
123
|
field[:target_col] = target_col.to_sym
|
98
124
|
field[:sql] = "(SELECT #{field[:target_col]} FROM #{field[:target_table]} WHERE id = NEW.#{field[:col]})"
|
99
125
|
field[:trigger_sql] = true # create_secondary_trigger_sql(field[:target_table], field[:target_col], field[:col])
|
100
126
|
field[:target_table_alias] = "#{field[:target_table]}_#{name}"
|
127
|
+
elsif field[:source]
|
128
|
+
field[:source] = field[:source].downcase
|
129
|
+
target_table, target_col = field[:source].split(".")
|
130
|
+
field[:target_table] = target_table.to_sym
|
131
|
+
field[:target_col] = target_col.to_sym
|
132
|
+
field[:conditions_sql] = field[:conditions].collect{|k, v| "#{k} = '#{v}'"}.join(" AND ") if field[:conditions]
|
133
|
+
field[:sql] = "SELECT #{field[:target_col]} FROM #{field[:target_table]} WHERE #{field[:reference]} = NEW.id"
|
134
|
+
field[:sql] += " AND #{field[:conditions_sql]}" if field[:conditions_sql]
|
135
|
+
field[:sql] = "(#{field[:sql]})"
|
136
|
+
field[:trigger_sql] = true
|
137
|
+
field[:target_table_alias] = "#{field[:target_table]}_#{name}"
|
101
138
|
else
|
102
139
|
field[:col] = name unless field[:col]
|
103
140
|
field[:sql] = field[:col]
|
@@ -109,7 +146,7 @@ class Litesearch::Schema::BackedAdapter < Litesearch::Schema::ContentlessAdapter
|
|
109
146
|
|
110
147
|
def filter_sql
|
111
148
|
sql = ""
|
112
|
-
sql << "
|
149
|
+
sql << " WHERE #{@schema[:filter_column]} = TRUE " if @schema[:filter_column]
|
113
150
|
sql
|
114
151
|
end
|
115
152
|
|
@@ -124,20 +161,27 @@ class Litesearch::Schema::BackedAdapter < Litesearch::Schema::ContentlessAdapter
|
|
124
161
|
(!field[:trigger_sql].nil?) ? "#{field[:target_table_alias]}.#{field[:target_col]}" : field[:target]
|
125
162
|
end.join(", ")
|
126
163
|
end
|
127
|
-
|
128
|
-
def
|
129
|
-
|
164
|
+
|
165
|
+
def joins_sql
|
166
|
+
joins = [@schema[:table]]
|
130
167
|
active_fields.each do |name, field|
|
131
|
-
|
168
|
+
if field[:trigger_sql]
|
169
|
+
join_table = ""
|
170
|
+
join_table << "#{field[:target_table]} AS #{field[:target_table_alias]} ON "
|
171
|
+
if field[:col]
|
172
|
+
join_table << "#{field[:target_table_alias]}.id = #{@schema[:table]}.#{field[:col]}" if field[:col]
|
173
|
+
elsif field[:source]
|
174
|
+
join_table << "#{field[:target_table_alias]}.#{field[:reference]} = #{@schema[:table]}.id"
|
175
|
+
if field[:conditions]
|
176
|
+
join_table << " AND "
|
177
|
+
join_table << field[:conditions].collect{|k, v| "#{field[:target_table_alias]}.#{k} = '#{v}'"}.join(" AND ")
|
178
|
+
end
|
179
|
+
end
|
180
|
+
joins << join_table
|
181
|
+
end
|
132
182
|
end
|
133
|
-
|
183
|
+
joins.join(" LEFT JOIN ")
|
134
184
|
end
|
135
185
|
|
136
|
-
|
137
|
-
conditions = []
|
138
|
-
active_fields.each do |name, field|
|
139
|
-
conditions << "#{field[:target_table_alias]}.id = #{@schema[:table]}.#{field[:col]}" if field[:trigger_sql]
|
140
|
-
end
|
141
|
-
conditions.join(" AND ")
|
142
|
-
end
|
186
|
+
|
143
187
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require_relative "
|
2
|
-
require_relative "
|
3
|
-
require_relative "
|
4
|
-
require_relative "
|
1
|
+
require_relative "schema_adapters/basic_adapter"
|
2
|
+
require_relative "schema_adapters/standalone_adapter"
|
3
|
+
require_relative "schema_adapters/contentless_adapter"
|
4
|
+
require_relative "schema_adapters/backed_adapter"
|
data/lib/litestack/litesearch.rb
CHANGED