litestack 0.2.6 → 0.4.1
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/BENCHMARKS.md +11 -0
- data/CHANGELOG.md +19 -0
- data/Gemfile +2 -0
- data/README.md +1 -1
- data/assets/event_page.png +0 -0
- data/assets/index_page.png +0 -0
- data/assets/topic_page.png +0 -0
- data/bench/bench_jobs_rails.rb +1 -1
- data/bench/bench_jobs_raw.rb +1 -1
- data/bench/uljob.rb +1 -1
- data/lib/action_cable/subscription_adapter/litecable.rb +1 -11
- data/lib/active_support/cache/litecache.rb +1 -1
- data/lib/generators/litestack/install/templates/database.yml +5 -1
- data/lib/litestack/liteboard/liteboard.rb +172 -35
- data/lib/litestack/liteboard/views/index.erb +52 -20
- data/lib/litestack/liteboard/views/layout.erb +189 -38
- data/lib/litestack/liteboard/views/litecable.erb +118 -0
- data/lib/litestack/liteboard/views/litecache.erb +144 -0
- data/lib/litestack/liteboard/views/litedb.erb +168 -0
- data/lib/litestack/liteboard/views/litejob.erb +151 -0
- data/lib/litestack/litecable.rb +27 -37
- data/lib/litestack/litecable.sql.yml +1 -1
- data/lib/litestack/litecache.rb +7 -18
- data/lib/litestack/litedb.rb +17 -2
- data/lib/litestack/litejob.rb +2 -3
- data/lib/litestack/litejobqueue.rb +51 -48
- data/lib/litestack/litemetric.rb +46 -69
- data/lib/litestack/litemetric.sql.yml +14 -12
- data/lib/litestack/litemetric_collector.sql.yml +4 -4
- data/lib/litestack/litequeue.rb +9 -20
- data/lib/litestack/litescheduler.rb +84 -0
- data/lib/litestack/litesearch/index.rb +230 -0
- data/lib/litestack/litesearch/model.rb +178 -0
- data/lib/litestack/litesearch/schema.rb +193 -0
- data/lib/litestack/litesearch/schema_adapters/backed_adapter.rb +147 -0
- data/lib/litestack/litesearch/schema_adapters/basic_adapter.rb +128 -0
- data/lib/litestack/litesearch/schema_adapters/contentless_adapter.rb +17 -0
- data/lib/litestack/litesearch/schema_adapters/standalone_adapter.rb +33 -0
- data/lib/litestack/litesearch/schema_adapters.rb +9 -0
- data/lib/litestack/litesearch.rb +37 -0
- data/lib/litestack/litesupport.rb +55 -125
- data/lib/litestack/version.rb +1 -1
- data/lib/litestack.rb +2 -1
- data/lib/sequel/adapters/litedb.rb +3 -2
- metadata +20 -3
@@ -0,0 +1,147 @@
|
|
1
|
+
class Litesearch::Schema::BackedAdapter < Litesearch::Schema::ContentlessAdapter
|
2
|
+
|
3
|
+
private
|
4
|
+
|
5
|
+
def table
|
6
|
+
@schema[:table]
|
7
|
+
end
|
8
|
+
|
9
|
+
def generate_sql
|
10
|
+
super
|
11
|
+
@sql[:rebuild] = :rebuild_sql
|
12
|
+
@sql[:drop_primary_triggers] = :drop_primary_triggers_sql
|
13
|
+
@sql[:drop_secondary_triggers] = :drop_secondary_triggers_sql
|
14
|
+
@sql[:create_primary_triggers] = :create_primary_triggers_sql
|
15
|
+
@sql[:create_secondary_triggers] = :create_secondary_triggers_sql
|
16
|
+
end
|
17
|
+
|
18
|
+
def drop_primary_triggers_sql
|
19
|
+
sql = <<-SQL
|
20
|
+
DROP TRIGGER IF EXISTS #{name}_insert;
|
21
|
+
DROP TRIGGER IF EXISTS #{name}_update;
|
22
|
+
DROP TRIGGER IF EXISTS #{name}_update_not;
|
23
|
+
DROP TRIGGER IF EXISTS #{name}_delete;
|
24
|
+
SQL
|
25
|
+
end
|
26
|
+
|
27
|
+
def create_primary_triggers_sql(active=false)
|
28
|
+
when_stmt = "TRUE"
|
29
|
+
cols = active_cols_names
|
30
|
+
if filter = @schema[:filter_column]
|
31
|
+
when_stmt = "NEW.#{filter} = TRUE"
|
32
|
+
cols << filter
|
33
|
+
end
|
34
|
+
|
35
|
+
sql = <<-SQL
|
36
|
+
CREATE TRIGGER #{name}_insert AFTER INSERT ON #{table} WHEN #{when_stmt} BEGIN
|
37
|
+
INSERT OR REPLACE INTO #{name}(rowid, #{active_field_names.join(', ')}) VALUES (NEW.rowid, #{trigger_cols_sql});
|
38
|
+
END;
|
39
|
+
CREATE TRIGGER #{name}_update AFTER UPDATE OF #{cols.join(', ')} ON #{table} WHEN #{when_stmt} BEGIN
|
40
|
+
INSERT OR REPLACE INTO #{name}(rowid, #{active_field_names.join(', ')}) VALUES (NEW.rowid, #{trigger_cols_sql});
|
41
|
+
END;
|
42
|
+
CREATE TRIGGER #{name}_update_not AFTER UPDATE OF #{cols.join(', ')} ON #{table} WHEN NOT #{when_stmt} BEGIN
|
43
|
+
DELETE FROM #{name} WHERE rowid = NEW.rowid;
|
44
|
+
END;
|
45
|
+
CREATE TRIGGER #{name}_delete AFTER DELETE ON #{table} BEGIN
|
46
|
+
DELETE FROM #{name} WHERE rowid = OLD.id;
|
47
|
+
END;
|
48
|
+
SQL
|
49
|
+
end
|
50
|
+
|
51
|
+
def drop_secondary_trigger_sql(target_table, target_col, col)
|
52
|
+
"DROP TRIGGER IF EXISTS #{target_table}_#{target_col}_#{col}_#{name}_update;"
|
53
|
+
end
|
54
|
+
|
55
|
+
def create_secondary_trigger_sql(target_table, target_col, col)
|
56
|
+
sql = <<-SQL
|
57
|
+
CREATE TRIGGER #{target_table}_#{target_col}_#{col}_#{name}_update AFTER UPDATE OF #{target_col} ON #{target_table} BEGIN
|
58
|
+
#{rebuild_sql} AND #{table}.#{col} = NEW.id;
|
59
|
+
END;
|
60
|
+
SQL
|
61
|
+
end
|
62
|
+
|
63
|
+
def drop_secondary_triggers_sql
|
64
|
+
sql = ""
|
65
|
+
@schema[:fields].each do |name, field|
|
66
|
+
if field[:trigger_sql]
|
67
|
+
sql << drop_secondary_trigger_sql(field[:target_table], field[:target_col], field[:col])
|
68
|
+
end
|
69
|
+
end
|
70
|
+
return sql.empty? ? nil : sql
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
def create_secondary_triggers_sql
|
75
|
+
sql = ""
|
76
|
+
@schema[:fields].each do |name, field|
|
77
|
+
if field[:trigger_sql]
|
78
|
+
sql << create_secondary_trigger_sql(field[:target_table], field[:target_col], field[:col])
|
79
|
+
end
|
80
|
+
end
|
81
|
+
return sql.empty? ? nil : sql
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
def rebuild_sql
|
86
|
+
conditions = ""
|
87
|
+
jcs = join_conditions_sql
|
88
|
+
fs = filter_sql
|
89
|
+
conditions = " ON #{jcs} #{fs}" unless jcs.empty? && fs.empty?
|
90
|
+
"INSERT OR REPLACE INTO #{name}(rowid, #{active_field_names.join(', ')}) SELECT #{table}.id, #{select_cols_sql} FROM #{join_tables_sql} #{conditions}"
|
91
|
+
end
|
92
|
+
|
93
|
+
def enrich_schema
|
94
|
+
@schema[:fields].each do |name, field|
|
95
|
+
if field[:target] && ! field[:target].start_with?("#{table}.")
|
96
|
+
field[:target] = field[:target].downcase
|
97
|
+
target_table, target_col = field[:target].split('.')
|
98
|
+
field[:col] = "#{name}_id".to_sym unless field[:col]
|
99
|
+
field[:target_table] = target_table.to_sym
|
100
|
+
field[:target_col] = target_col.to_sym
|
101
|
+
field[:sql] = "(SELECT #{field[:target_col]} FROM #{field[:target_table]} WHERE id = NEW.#{field[:col]})"
|
102
|
+
field[:trigger_sql] = true # create_secondary_trigger_sql(field[:target_table], field[:target_col], field[:col])
|
103
|
+
field[:target_table_alias] = "#{field[:target_table]}_#{name}"
|
104
|
+
else
|
105
|
+
field[:col] = name unless field[:col]
|
106
|
+
field[:sql] = field[:col]
|
107
|
+
field[:target_table] = @schema[:table]
|
108
|
+
field[:target] = "#{@schema[:table]}.#{field[:sql]}"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def filter_sql
|
114
|
+
sql = ""
|
115
|
+
sql << " AND #{@schema[:filter_column]} = TRUE " if @schema[:filter_column]
|
116
|
+
sql
|
117
|
+
end
|
118
|
+
|
119
|
+
def trigger_cols_sql
|
120
|
+
active_fields.collect do |name, field|
|
121
|
+
field[:trigger_sql] ? field[:sql] : "NEW.#{field[:sql]}"
|
122
|
+
end.join(", ")
|
123
|
+
end
|
124
|
+
|
125
|
+
def select_cols_sql
|
126
|
+
active_fields.collect do |name, field|
|
127
|
+
field[:trigger_sql] != nil ? "#{field[:target_table_alias]}.#{field[:target_col]}" : field[:target]
|
128
|
+
end.join(', ')
|
129
|
+
end
|
130
|
+
|
131
|
+
def join_tables_sql
|
132
|
+
tables = [@schema[:table]]
|
133
|
+
active_fields.each do |name, field|
|
134
|
+
tables << "#{field[:target_table]} AS #{field[:target_table_alias]}" if field[:trigger_sql]
|
135
|
+
end
|
136
|
+
tables.uniq.join(", ")
|
137
|
+
end
|
138
|
+
|
139
|
+
def join_conditions_sql
|
140
|
+
conditions = []
|
141
|
+
active_fields.each do |name, field|
|
142
|
+
conditions << "#{field[:target_table_alias]}.id = #{@schema[:table]}.#{field[:col]}" if field[:trigger_sql]
|
143
|
+
end
|
144
|
+
conditions.join(" AND ")
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
class Litesearch::Schema::BasicAdapter
|
2
|
+
|
3
|
+
def initialize(schema)
|
4
|
+
@schema = schema
|
5
|
+
@sql = {}
|
6
|
+
enrich_schema
|
7
|
+
generate_sql
|
8
|
+
end
|
9
|
+
|
10
|
+
def name
|
11
|
+
@schema[:name]
|
12
|
+
end
|
13
|
+
|
14
|
+
def table
|
15
|
+
@schema[:table]
|
16
|
+
end
|
17
|
+
|
18
|
+
def fields
|
19
|
+
@schema[:fields]
|
20
|
+
end
|
21
|
+
|
22
|
+
def field_names
|
23
|
+
@schema[:fields].keys
|
24
|
+
end
|
25
|
+
|
26
|
+
def active_fields
|
27
|
+
@schema[:fields].select{|k, v| v[:weight] != 0 }
|
28
|
+
end
|
29
|
+
|
30
|
+
def active_field_names
|
31
|
+
active_fields.keys
|
32
|
+
end
|
33
|
+
|
34
|
+
def active_cols_names
|
35
|
+
active_fields.collect{|k, v| v[:col]}
|
36
|
+
end
|
37
|
+
|
38
|
+
def weights
|
39
|
+
@schema[:fields].values.collect{|v| v[:weight].to_f }
|
40
|
+
end
|
41
|
+
|
42
|
+
def active_weights
|
43
|
+
active_fields.values.collect{|v| v[:weight].to_f }
|
44
|
+
end
|
45
|
+
|
46
|
+
def tokenizer_sql
|
47
|
+
Litesearch::Schema::TOKENIZERS[@schema[:tokenizer]]
|
48
|
+
end
|
49
|
+
|
50
|
+
def order_fields(old_schema)
|
51
|
+
new_fields = {}
|
52
|
+
old_field_names = old_schema.schema[:fields].keys
|
53
|
+
old_field_names.each do |name|
|
54
|
+
new_fields[name] = @schema[:fields].delete(name)
|
55
|
+
end
|
56
|
+
missing_field_names = field_names - old_field_names
|
57
|
+
missing_field_names.each do |name|
|
58
|
+
new_fields[name] = @schema[:fields].delete(name)
|
59
|
+
end
|
60
|
+
@schema[:fields] = new_fields # this should be in order now
|
61
|
+
generate_sql
|
62
|
+
enrich_schema
|
63
|
+
end
|
64
|
+
|
65
|
+
def sql_for(method, *args)
|
66
|
+
if sql = @sql[method]
|
67
|
+
if sql.is_a? String
|
68
|
+
return sql
|
69
|
+
elsif sql.is_a? Proc
|
70
|
+
return sql.call(*args)
|
71
|
+
elsif sql.is_a? Symbol
|
72
|
+
return self.send(sql, *args)
|
73
|
+
elsif sql.is_a? Litesearch::SchemaChangeException
|
74
|
+
raise sql
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def generate_sql
|
80
|
+
@sql[:create_index] = :create_index_sql
|
81
|
+
@sql[:insert] = "INSERT OR REPLACE INTO #{name}(rowid, #{active_col_names_sql}) VALUES (:id, #{active_col_names_var_sql}) RETURNING rowid"
|
82
|
+
@sql[:delete] = "DELETE FROM #{name} WHERE rowid = :id"
|
83
|
+
@sql[:count] = "SELECT count(*) FROM #{name}(:term)"
|
84
|
+
@sql[:count_all] = "SELECT count(*) FROM #{name}"
|
85
|
+
@sql[:delete_all] = "DELETE FROM #{name}"
|
86
|
+
@sql[:drop] = "DROP TABLE #{name}"
|
87
|
+
@sql[:expand_data] = "UPDATE #{name}_data SET block = block || zeroblob(:length) WHERE id = 1"
|
88
|
+
@sql[:expand_docsize] = "UPDATE #{name}_docsize SET sz = sz || zeroblob(:length)"
|
89
|
+
@sql[:ranks] = :ranks_sql
|
90
|
+
@sql[:set_config_value] = "INSERT OR REPLACE INTO #{name}_config(k, v) VALUES (:key, :value)"
|
91
|
+
@sql[:get_config_value] = "SELECT v FROM #{name}_config WHERE k = :key"
|
92
|
+
@sql[:search] = "SELECT rowid AS id, -rank AS search_rank FROM #{name}(:term) WHERE rank !=0 ORDER BY rank LIMIT :limit OFFSET :offset"
|
93
|
+
@sql[:update_index] = "UPDATE sqlite_schema SET sql = :sql WHERE name = '#{name}'"
|
94
|
+
@sql[:update_content_table] = "UPDATE sqlite_schema SET sql = :sql WHERE name = '#{name}_content'"
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def ranks_sql(active=false)
|
100
|
+
if active
|
101
|
+
weights_sql = weights.join(', ')
|
102
|
+
else
|
103
|
+
weights_sql = active_weights.join(', ')
|
104
|
+
end
|
105
|
+
"INSERT INTO #{name}(#{name}, rank) VALUES ('rank', 'bm25(#{weights_sql})')"
|
106
|
+
end
|
107
|
+
|
108
|
+
def active_col_names_sql
|
109
|
+
active_field_names.join(', ')
|
110
|
+
end
|
111
|
+
|
112
|
+
def active_col_names_var_sql
|
113
|
+
":#{active_field_names.join(', :')}"
|
114
|
+
end
|
115
|
+
|
116
|
+
def col_names_sql
|
117
|
+
field_names.join(', ')
|
118
|
+
end
|
119
|
+
|
120
|
+
def col_names_var_sql
|
121
|
+
":#{field_names.join(', :')}"
|
122
|
+
end
|
123
|
+
|
124
|
+
def enrich_schema
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Litesearch::Schema::ContentlessAdapter < Litesearch::Schema::BasicAdapter
|
2
|
+
|
3
|
+
private
|
4
|
+
|
5
|
+
def generate_sql
|
6
|
+
super
|
7
|
+
#@sql[:rebuild_index] = Litesearch::SchemaChangeException.new("You cannot rebuild a contentless index")
|
8
|
+
#@sql[:rebuild] = Litesearch::SchemaChangeException.new("You cannot rebuild a contentless index")
|
9
|
+
end
|
10
|
+
|
11
|
+
def create_index_sql(active = false)
|
12
|
+
col_names = active ? active_col_names_sql : col_names_sql
|
13
|
+
"CREATE VIRTUAL TABLE #{name} USING FTS5(#{col_names}, content='', contentless_delete=1, tokenize='#{tokenizer_sql}')"
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class Litesearch::Schema::StandaloneAdapter < Litesearch::Schema::BasicAdapter
|
2
|
+
|
3
|
+
def generate_sql
|
4
|
+
super
|
5
|
+
@sql[:move_content] = "ALTER TABLE #{name}_content RENAME TO #{name}_content_temp"
|
6
|
+
@sql[:adjust_temp_content] = "UPDATE sqlite_schema SET sql (SELECT sql FROM sqlite_schema WHERE name = '#{name}_content') WHERE name = #{name}_content_temp"
|
7
|
+
@sql[:restore_content] = "ALTER TABLE #{name}_content_temp RENAME TO #{name}_content"
|
8
|
+
@sql[:rebuild] = "INSERT INTO #{name}(#{name}) VALUES ('rebuild')"
|
9
|
+
@sql[:drop_content_table] = "DROP TABLE #{name}_content"
|
10
|
+
@sql[:drop_content_col] = :drop_content_col_sql
|
11
|
+
@sql[:create_content_table] = :create_content_table_sql
|
12
|
+
@sql[:search] = "SELECT rowid AS id, *, -rank AS search_rank FROM #{name}(:term) WHERE rank !=0 ORDER BY rank LIMIT :limit OFFSET :offset"
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def create_index_sql(active = false)
|
18
|
+
col_names = active ? active_col_names_sql : col_names_sql
|
19
|
+
"CREATE VIRTUAL TABLE #{name} USING FTS5(#{col_names}, tokenize='#{tokenizer_sql}')"
|
20
|
+
end
|
21
|
+
|
22
|
+
def drop_content_col_sql(col_index)
|
23
|
+
"ALTER TABLE #{name}_content DROP COLUMN c#{col_index}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def create_content_table_sql(count)
|
27
|
+
cols = []
|
28
|
+
count.times{|i| cols << "c#{i}" }
|
29
|
+
"CREATE TABLE #{name}_content(id INTEGER PRIMARY KEY, #{cols.join(', ')})"
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Litesearch
|
2
|
+
class Index; end
|
3
|
+
class Schema; end
|
4
|
+
end
|
5
|
+
|
6
|
+
require_relative './litesearch/index'
|
7
|
+
require_relative './litesearch/model'
|
8
|
+
|
9
|
+
module Litesearch
|
10
|
+
|
11
|
+
def litesearch_index_cache
|
12
|
+
@litesearch_index_cache ||= {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def search_index(name)
|
16
|
+
# normalize the index name
|
17
|
+
# find the index in the db cache
|
18
|
+
name = name.to_s.downcase.to_sym
|
19
|
+
index = litesearch_index_cache[name]
|
20
|
+
# if the index is in the cache and no block is given then return it
|
21
|
+
return index if index && !block_given?
|
22
|
+
# if either there is no index in the cache or a block is given
|
23
|
+
# create a new index instance and then place it in the cache and return
|
24
|
+
if block_given?
|
25
|
+
index = Index.new(self, name) do |schema|
|
26
|
+
yield schema
|
27
|
+
schema.name(name)
|
28
|
+
end
|
29
|
+
else
|
30
|
+
index = Index.new(self, name)
|
31
|
+
end
|
32
|
+
litesearch_index_cache[name] = index
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
|
@@ -7,14 +7,11 @@ require 'yaml'
|
|
7
7
|
require 'pathname'
|
8
8
|
require 'fileutils'
|
9
9
|
|
10
|
+
require_relative "./litescheduler"
|
11
|
+
|
10
12
|
module Litesupport
|
11
13
|
|
12
14
|
class Error < StandardError; end
|
13
|
-
|
14
|
-
def self.max_contexts
|
15
|
-
return 50 if scheduler == :fiber || scheduler == :polyphony
|
16
|
-
5
|
17
|
-
end
|
18
15
|
|
19
16
|
# Detect the Rack or Rails environment.
|
20
17
|
def self.detect_environment
|
@@ -23,7 +20,7 @@ module Litesupport
|
|
23
20
|
elsif ENV["RACK_ENV"]
|
24
21
|
ENV["RACK_ENV"]
|
25
22
|
elsif ENV["APP_ENV"]
|
26
|
-
ENV["
|
23
|
+
ENV["APP_ENV"]
|
27
24
|
else
|
28
25
|
"development"
|
29
26
|
end
|
@@ -32,89 +29,11 @@ module Litesupport
|
|
32
29
|
def self.environment
|
33
30
|
@environment ||= detect_environment
|
34
31
|
end
|
35
|
-
|
36
|
-
# cache the scheduler we are running in
|
37
|
-
# it is an error to change the scheduler for a process
|
38
|
-
# or for a child forked from that process
|
39
|
-
def self.scheduler
|
40
|
-
@scehduler ||= detect_scheduler
|
41
|
-
end
|
42
|
-
|
43
|
-
# identify which scheduler we are running in
|
44
|
-
# we currently support :fiber, :polyphony, :iodine & :threaded
|
45
|
-
# in the future we might want to expand to other schedulers
|
46
|
-
def self.detect_scheduler
|
47
|
-
return :fiber if Fiber.scheduler
|
48
|
-
return :polyphony if defined? Polyphony
|
49
|
-
return :iodine if defined? Iodine
|
50
|
-
return :threaded # fall back for all other schedulers
|
51
|
-
end
|
52
|
-
|
53
|
-
# spawn a new execution context
|
54
|
-
def self.spawn(&block)
|
55
|
-
if self.scheduler == :fiber
|
56
|
-
Fiber.schedule(&block)
|
57
|
-
elsif self.scheduler == :polyphony
|
58
|
-
spin(&block)
|
59
|
-
elsif self.scheduler == :threaded or self.scheduler == :iodine
|
60
|
-
Thread.new(&block)
|
61
|
-
end
|
62
|
-
# we should never reach here
|
63
|
-
end
|
64
|
-
|
65
|
-
def self.context
|
66
|
-
if scheduler == :fiber || scheduler == :poylphony
|
67
|
-
Fiber.current.storage
|
68
|
-
else
|
69
|
-
Thread.current
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
def self.current_context
|
74
|
-
if scheduler == :fiber || scheduler == :poylphony
|
75
|
-
Fiber.current
|
76
|
-
else
|
77
|
-
Thread.current
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
# switch the execution context to allow others to run
|
82
|
-
def self.switch
|
83
|
-
if self.scheduler == :fiber
|
84
|
-
Fiber.scheduler.yield
|
85
|
-
true
|
86
|
-
elsif self.scheduler == :polyphony
|
87
|
-
Fiber.current.schedule
|
88
|
-
Thread.current.switch_fiber
|
89
|
-
true
|
90
|
-
else
|
91
|
-
#Thread.pass
|
92
|
-
false
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
# mutex initialization
|
97
|
-
def self.mutex
|
98
|
-
# a single mutex per process (is that ok?)
|
99
|
-
@@mutex ||= Mutex.new
|
100
|
-
end
|
101
|
-
|
102
|
-
# bold assumption, we will only synchronize threaded code
|
103
|
-
# if some code explicitly wants to synchronize a fiber
|
104
|
-
# they must send (true) as a parameter to this method
|
105
|
-
# else it is a no-op for fibers
|
106
|
-
def self.synchronize(fiber_sync = false, &block)
|
107
|
-
if self.scheduler == :fiber or self.scheduler == :polyphony
|
108
|
-
yield # do nothing, just run the block as is
|
109
|
-
else
|
110
|
-
self.mutex.synchronize(&block)
|
111
|
-
end
|
112
|
-
end
|
113
32
|
|
114
33
|
# common db object options
|
115
34
|
def self.create_db(path)
|
116
35
|
db = SQLite3::Database.new(path)
|
117
|
-
db.busy_handler{ switch || sleep(0.0001) }
|
36
|
+
db.busy_handler{ Litescheduler.switch || sleep(0.0001) }
|
118
37
|
db.journal_mode = "WAL"
|
119
38
|
db.instance_variable_set(:@stmts, {})
|
120
39
|
class << db
|
@@ -153,7 +72,7 @@ module Litesupport
|
|
153
72
|
end
|
154
73
|
|
155
74
|
def synchronize(&block)
|
156
|
-
if
|
75
|
+
if Litescheduler.backend == :threaded || Litescheduler.backend == :iodine
|
157
76
|
@mutex.synchronize{ block.call }
|
158
77
|
else
|
159
78
|
block.call
|
@@ -162,57 +81,32 @@ module Litesupport
|
|
162
81
|
|
163
82
|
end
|
164
83
|
|
165
|
-
module Forkable
|
166
|
-
|
167
|
-
def _fork(*args)
|
168
|
-
ppid = Process.pid
|
169
|
-
result = super
|
170
|
-
if Process.pid != ppid
|
171
|
-
# trigger a restart of all connections owned by Litesupport::Pool
|
172
|
-
end
|
173
|
-
result
|
174
|
-
end
|
175
|
-
|
176
|
-
end
|
177
|
-
|
178
|
-
#::Process.singleton_class.prepend(::Litesupport::Forkable)
|
179
|
-
|
180
84
|
class Pool
|
181
85
|
|
182
86
|
def initialize(count, &block)
|
183
87
|
@count = count
|
184
88
|
@block = block
|
185
|
-
@resources =
|
89
|
+
@resources = Thread::Queue.new
|
186
90
|
@mutex = Litesupport::Mutex.new
|
187
91
|
@count.times do
|
188
92
|
resource = @mutex.synchronize{ block.call }
|
189
|
-
@resources <<
|
93
|
+
@resources << resource
|
190
94
|
end
|
191
95
|
end
|
192
96
|
|
193
97
|
def acquire
|
194
|
-
# check for pid changes
|
195
|
-
acquired = false
|
196
98
|
result = nil
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
raise e
|
205
|
-
ensure
|
206
|
-
resource[1] = :free
|
207
|
-
acquired = true
|
208
|
-
end
|
209
|
-
end
|
210
|
-
end
|
211
|
-
sleep 0.001 unless acquired
|
99
|
+
resource = @resources.pop
|
100
|
+
begin
|
101
|
+
result = yield resource
|
102
|
+
rescue Exception => e
|
103
|
+
raise e
|
104
|
+
ensure
|
105
|
+
@resources << resource
|
212
106
|
end
|
213
107
|
result
|
214
108
|
end
|
215
|
-
|
109
|
+
|
216
110
|
end
|
217
111
|
|
218
112
|
module ForkListener
|
@@ -230,7 +124,7 @@ module Litesupport
|
|
230
124
|
def _fork(*args)
|
231
125
|
ppid = Process.pid
|
232
126
|
result = super
|
233
|
-
if Process.pid != ppid && [:threaded, :iodine].include?(
|
127
|
+
if Process.pid != ppid && [:threaded, :iodine].include?(Litescheduler.backend)
|
234
128
|
ForkListener.listeners.each{|l| l.call }
|
235
129
|
end
|
236
130
|
result
|
@@ -333,15 +227,20 @@ module Litesupport
|
|
333
227
|
def run_method(method, *args)
|
334
228
|
@conn.acquire{|q| q.send(method, *args)}
|
335
229
|
end
|
230
|
+
|
231
|
+
def run_stmt_method(stmt, method, *args)
|
232
|
+
@conn.acquire{|q| q.stmts[stmt].send(method, *args)}
|
233
|
+
end
|
234
|
+
|
336
235
|
|
337
236
|
def create_pooled_connection(count = 1)
|
338
237
|
Litesupport::Pool.new(1){create_connection}
|
339
238
|
end
|
340
239
|
|
341
240
|
# common db object options
|
342
|
-
def create_connection
|
241
|
+
def create_connection(path_to_sql_file = nil)
|
343
242
|
conn = SQLite3::Database.new(@options[:path])
|
344
|
-
conn.busy_handler{
|
243
|
+
conn.busy_handler{ Litescheduler.switch || sleep(rand * 0.002) }
|
345
244
|
conn.journal_mode = "WAL"
|
346
245
|
conn.synchronous = @options[:sync] || 1
|
347
246
|
conn.mmap_size = @options[:mmap_size] || 0
|
@@ -349,9 +248,40 @@ module Litesupport
|
|
349
248
|
class << conn
|
350
249
|
attr_reader :stmts
|
351
250
|
end
|
251
|
+
yield conn if block_given?
|
252
|
+
# use the <client>.sql.yml file to define the schema and compile prepared statements
|
253
|
+
unless path_to_sql_file.nil?
|
254
|
+
sql = YAML.load_file(path_to_sql_file)
|
255
|
+
version = conn.get_first_value("PRAGMA user_version")
|
256
|
+
sql["schema"].each_pair do |v, obj|
|
257
|
+
if v > version
|
258
|
+
conn.transaction do
|
259
|
+
obj.each do |k, s|
|
260
|
+
begin
|
261
|
+
conn.execute(s)
|
262
|
+
rescue Exception => e
|
263
|
+
STDERR.puts "Error parsing #{k}"
|
264
|
+
STDERR.puts s
|
265
|
+
raise e
|
266
|
+
end
|
267
|
+
end
|
268
|
+
conn.user_version = v
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
sql["stmts"].each do |k, v|
|
273
|
+
begin
|
274
|
+
conn.stmts[k.to_sym] = conn.prepare(v)
|
275
|
+
rescue Exception => e
|
276
|
+
STDERR.puts "Error parsing #{k}"
|
277
|
+
STDERR.puts v
|
278
|
+
raise e
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
352
282
|
conn
|
353
283
|
end
|
354
|
-
|
284
|
+
|
355
285
|
end
|
356
286
|
|
357
287
|
end
|
data/lib/litestack/version.rb
CHANGED
data/lib/litestack.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
# load core classes
|
4
4
|
require_relative "./litestack/version"
|
5
|
+
require_relative "./litestack/litescheduler"
|
5
6
|
require_relative "./litestack/litesupport"
|
6
7
|
require_relative "./litestack/litemetric"
|
7
8
|
require_relative "./litestack/litedb"
|
@@ -16,7 +17,7 @@ require_relative "./railties/rails/commands/dbconsole" if defined? Rails && defi
|
|
16
17
|
require_relative "./active_support/cache/litecache" if defined? ActiveSupport
|
17
18
|
require_relative "./active_job/queue_adapters/litejob_adapter" if defined? ActiveJob
|
18
19
|
require_relative "./action_cable/subscription_adapter/litecable" if defined? ActionCable
|
19
|
-
require_relative "./litestack/railtie" if defined? Rails
|
20
|
+
require_relative "./litestack/railtie" if defined? Rails::Railtie
|
20
21
|
|
21
22
|
module Litestack
|
22
23
|
class NotImplementedError < Exception; end
|
@@ -1,10 +1,10 @@
|
|
1
1
|
require_relative '../../litestack/litedb'
|
2
2
|
require 'sequel'
|
3
3
|
require 'sequel/adapters/sqlite'
|
4
|
-
#require 'shared/litedb'
|
5
4
|
|
6
5
|
module Sequel
|
7
6
|
module Litedb
|
7
|
+
|
8
8
|
include SQLite
|
9
9
|
|
10
10
|
LITEDB_TYPES = SQLITE_TYPES
|
@@ -15,7 +15,7 @@ module Sequel
|
|
15
15
|
|
16
16
|
def connect(server)
|
17
17
|
|
18
|
-
Sequel.extension :fiber_concurrency if [:fiber, :polyphony].include?
|
18
|
+
Sequel.extension :fiber_concurrency if [:fiber, :polyphony].include? Litescheduler.backend
|
19
19
|
|
20
20
|
opts = server_opts(server)
|
21
21
|
opts[:database] = ':memory:' if blank_object?(opts[:database])
|
@@ -36,6 +36,7 @@ module Sequel
|
|
36
36
|
end
|
37
37
|
|
38
38
|
db.instance_variable_set(:@prepared_statements, {})
|
39
|
+
@raw_db = db
|
39
40
|
db
|
40
41
|
end
|
41
42
|
|