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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.standard.yml +3 -1
  3. data/BENCHMARKS.md +3 -3
  4. data/CAVEATS.md +20 -0
  5. data/CHANGELOG.md +19 -4
  6. data/FILESYSTEMS.md +55 -0
  7. data/Gemfile +2 -0
  8. data/README.md +2 -2
  9. data/ROADMAP.md +6 -6
  10. data/assets/litestack_advantage.png +0 -0
  11. data/bench/bench.rb +2 -0
  12. data/bench/bench_cache_rails.rb +14 -17
  13. data/bench/bench_cache_raw.rb +18 -15
  14. data/bench/bench_jobs_rails.rb +3 -3
  15. data/bench/bench_jobs_raw.rb +3 -3
  16. data/bin/liteboard +16 -14
  17. data/gemfiles/rails70.gemfile +5 -0
  18. data/gemfiles/rails71.gemfile +5 -0
  19. data/gemfiles/rails71.gemfile.lock +264 -0
  20. data/lib/active_job/queue_adapters/litejob_adapter.rb +11 -3
  21. data/lib/active_record/connection_adapters/litedb_adapter.rb +17 -7
  22. data/lib/active_support/cache/litecache.rb +25 -15
  23. data/lib/generators/litestack/install/install_generator.rb +2 -2
  24. data/lib/litestack/liteboard/liteboard.rb +15 -19
  25. data/lib/litestack/liteboard/views/litecable.erb +1 -1
  26. data/lib/litestack/litecable.rb +1 -1
  27. data/lib/litestack/litecache.rb +23 -25
  28. data/lib/litestack/litedb.rb +5 -1
  29. data/lib/litestack/litejob.rb +1 -1
  30. data/lib/litestack/litejobqueue.rb +24 -14
  31. data/lib/litestack/litemetric.rb +7 -6
  32. data/lib/litestack/litequeue.rb +17 -2
  33. data/lib/litestack/litequeue.sql.yml +38 -5
  34. data/lib/litestack/litescheduler.rb +1 -2
  35. data/lib/litestack/litesearch/index.rb +11 -10
  36. data/lib/litestack/litesearch/model.rb +61 -3
  37. data/lib/litestack/litesearch/schema.rb +7 -2
  38. data/lib/litestack/litesearch/schema_adapters/backed_adapter.rb +69 -25
  39. data/lib/litestack/litesearch/schema_adapters.rb +4 -4
  40. data/lib/litestack/litesearch.rb +2 -2
  41. data/lib/litestack/litesupport.rb +7 -6
  42. data/lib/litestack/railtie.rb +4 -2
  43. data/lib/litestack/version.rb +1 -1
  44. data/lib/litestack.rb +15 -15
  45. data/lib/sequel/adapters/litedb.rb +9 -1
  46. data/lib/sequel/adapters/shared/litedb.rb +2 -2
  47. data/scripts/build_metrics.rb +2 -2
  48. data/scripts/test_cable.rb +1 -1
  49. metadata +95 -59
@@ -1,6 +1,6 @@
1
1
  # frozen_stringe_literal: true
2
2
 
3
- require_relative "./litejobqueue"
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 "./litequeue"
4
- require_relative "./litemetric"
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 overriden by passing new options in a hash
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
- return unless @jobs_in_flight > 0
145
- puts "--- Litejob detected an exit, cleaning up"
146
- index = 0
147
- while @jobs_in_flight > 0 && index < 30 # 3 seconds grace period for jobs to finish
148
- puts "--- Waiting for #{@jobs_in_flight} jobs to finish"
149
- sleep 0.1
150
- index += 1
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
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "singleton"
4
4
 
5
- require_relative "./litesupport"
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.flush
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
@@ -13,7 +13,7 @@ require_relative "litesupport"
13
13
 
14
14
  class Litequeue
15
15
  # the default options for the queue
16
- # can be overriden by passing new options in a hash
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, reutrns the count of entries in that queue
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 INTEGER DEFAULT(unixepoch()) NOT NULL ON CONFLICT REPLACE,
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,
@@ -82,9 +82,8 @@ module Litescheduler
82
82
  @@mutex ||= Mutex.new
83
83
  end
84
84
 
85
- private
86
-
87
85
  def self.fiber_backed?
88
86
  backend == :fiber || backend == :polyphony
89
87
  end
88
+ private_class_method :fiber_backed?
90
89
  end
@@ -1,5 +1,5 @@
1
1
  require "oj"
2
- require_relative "./schema"
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
- do_rebuild
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
- rs = @stmts[:similar].execute(id, limit)
111
+ @stmts[:similar].execute(id, limit) # standard:disable Style/IdenticalConditionalBranches
112
112
  else
113
- rs = @stmts[:similar].execute(id, limit)
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 feild order
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 ot the query
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 "./schema_adapters"
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 OF #{cols.join(", ")} ON #{table} WHEN #{when_stmt} BEGIN
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 OF #{cols.join(", ")} ON #{table} WHEN NOT #{when_stmt} BEGIN
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
- sql << drop_secondary_trigger_sql(field[:target_table], field[:target_col], field[:col])
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
- sql << create_secondary_trigger_sql(field[:target_table], field[:target_col], field[:col])
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
- conditions = ""
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".to_sym unless field[:col]
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 << " AND #{@schema[:filter_column]} = TRUE " if @schema[:filter_column]
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 join_tables_sql
129
- tables = [@schema[:table]]
164
+
165
+ def joins_sql
166
+ joins = [@schema[:table]]
130
167
  active_fields.each do |name, field|
131
- tables << "#{field[:target_table]} AS #{field[:target_table_alias]}" if field[:trigger_sql]
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
- tables.uniq.join(", ")
183
+ joins.join(" LEFT JOIN ")
134
184
  end
135
185
 
136
- def join_conditions_sql
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 "./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"
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"
@@ -4,8 +4,8 @@ module Litesearch
4
4
  class Schema; end
5
5
  end
6
6
 
7
- require_relative "./litesearch/index"
8
- require_relative "./litesearch/model"
7
+ require_relative "litesearch/index"
8
+ require_relative "litesearch/model"
9
9
 
10
10
  module Litesearch
11
11
  def litesearch_index_cache