litestack 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +166 -0
- data/Rakefile +12 -0
- data/WHYLITESTACK.md +26 -0
- data/assets/litecache_logo_teal.png +0 -0
- data/assets/litedb_logo_teal.png +0 -0
- data/assets/litejob_logo_teal.png +0 -0
- data/assets/litestack_logo_teal.png +0 -0
- data/assets/litestack_logo_teal_large.png +0 -0
- data/bench/bench.rb +23 -0
- data/bench/bench_cache_rails.rb +67 -0
- data/bench/bench_cache_raw.rb +68 -0
- data/bench/bench_jobs_rails.rb +38 -0
- data/bench/bench_jobs_raw.rb +27 -0
- data/bench/bench_queue.rb +16 -0
- data/bench/bench_rails.rb +81 -0
- data/bench/bench_raw.rb +72 -0
- data/bench/rails_job.rb +18 -0
- data/bench/skjob.rb +13 -0
- data/bench/uljob.rb +15 -0
- data/lib/active_job/queue_adapters/litejob_adapter.rb +47 -0
- data/lib/active_job/queue_adapters/ultralite_adapter.rb +49 -0
- data/lib/active_record/connection_adapters/litedb_adapter.rb +102 -0
- data/lib/active_support/cache/litecache.rb +100 -0
- data/lib/active_support/cache/ultralite_cache_store.rb +100 -0
- data/lib/litestack/litecache.rb +254 -0
- data/lib/litestack/litedb.rb +47 -0
- data/lib/litestack/litejob.rb +84 -0
- data/lib/litestack/litejobqueue.rb +161 -0
- data/lib/litestack/litequeue.rb +105 -0
- data/lib/litestack/litesupport.rb +74 -0
- data/lib/litestack/version.rb +5 -0
- data/lib/litestack.rb +15 -0
- data/lib/railties/rails/commands/dbconsole.rb +87 -0
- data/lib/sequel/adapters/litedb.rb +43 -0
- data/samples/ultrajob.yaml +2 -0
- metadata +115 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
# all components should require the support module
|
2
|
+
require_relative 'litesupport'
|
3
|
+
|
4
|
+
# Litedb inherits from the SQLite3::Database class and adds a few initialization options
|
5
|
+
class Litedb < ::SQLite3::Database
|
6
|
+
|
7
|
+
# overrride the original initilaizer to allow for connection configuration
|
8
|
+
def initialize(file, options = {}, zfs = nil )
|
9
|
+
if block_given?
|
10
|
+
super(file, options, zfs) do |db|
|
11
|
+
init unless options[:noinit] == true
|
12
|
+
yield db
|
13
|
+
end
|
14
|
+
else
|
15
|
+
super(file, options, zfs)
|
16
|
+
init unless options[:noinit] == true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# enforce immediate mode to avoid deadlocks for a small performance penalty
|
21
|
+
def transaction(mode = :immediate)
|
22
|
+
super(mode)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
# default connection configuration values
|
28
|
+
def init
|
29
|
+
# version 3.37 is required for strict typing support and the newest json operators
|
30
|
+
raise Litesupport::Error if SQLite3::SQLITE_VERSION_NUMBER < 3037000
|
31
|
+
# time to wait to obtain a write lock before raising an exception
|
32
|
+
self.busy_handler{|i| sleep 0.001}
|
33
|
+
# level of database durability, 2 = "FULL" (sync on every write), other values include 1 = "NORMAL" (sync every 1000 written pages) and 0 = "NONE"
|
34
|
+
self.synchronous = 1
|
35
|
+
# Journal mode WAL allows for greater concurrency (many readers + one writer)
|
36
|
+
self.journal_mode = "WAL"
|
37
|
+
# impose a limit on the WAL file to prevent unlimited growth (with a negative impact on read performance as well)
|
38
|
+
self.journal_size_limit = 64 * 1024 * 1024
|
39
|
+
# set the global memory map so all processes can share data
|
40
|
+
self.mmap_size = 128 * 1024 * 1024
|
41
|
+
# increase the local connection cache to 2000 pages
|
42
|
+
self.cache_size = 2000
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_stringe_literal: true
|
2
|
+
|
3
|
+
require_relative './litejobqueue'
|
4
|
+
|
5
|
+
##
|
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.
|
7
|
+
#
|
8
|
+
#When a job is enqueued, Litejob creates a new instance of the class and passes it any necessary arguments. The class's #perform method is then called asynchronously to process the job. This allows the application to continue running without waiting for the job to finish, improving overall performance and responsiveness.
|
9
|
+
#
|
10
|
+
#One of the main benefits of using Litejob is its simplicity. Because it integrates directly with Litejobqueue, developers do not need to worry about managing job queues or processing logic themselves. Instead, they can focus on implementing the #perform method to handle the specific job tasks.
|
11
|
+
#
|
12
|
+
#Litejob also provides a number of useful features, including the ability to set job priorities, retry failed jobs, and limit the number of retries. These features can be configured using simple configuration options in the class that includes the Litejob module.
|
13
|
+
#
|
14
|
+
#Overall, Litejob is a powerful and flexible module that allows developers to easily integrate Litejobqueue job queueing into their Ruby applications. By enabling asynchronous job processing, Litejob can help improve application performance and scalability, while simplifying the development and management of background job processing logic.
|
15
|
+
# class EasyJob
|
16
|
+
# include ::Litejob
|
17
|
+
#
|
18
|
+
# def perform(params)
|
19
|
+
# # do stuff
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
#Then later you can perform a job asynchronously:
|
24
|
+
#
|
25
|
+
# EasyJob.perform_async(params) # perform a job synchronously
|
26
|
+
#Or perform it at a specific time:
|
27
|
+
# EasyJob.perform_at(time, params) # perform a job at a specific time
|
28
|
+
#Or perform it after a certain delay:
|
29
|
+
# EasyJob.perform_in(delay, params) # perform a job after a certain delay
|
30
|
+
#You can also specify a specific queue to be used
|
31
|
+
# class EasyJob
|
32
|
+
# include ::Litejob
|
33
|
+
#
|
34
|
+
# self.queue = :urgent
|
35
|
+
#
|
36
|
+
# def perform(params)
|
37
|
+
# # do stuff
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
module Litejob
|
42
|
+
|
43
|
+
private
|
44
|
+
def self.included(klass)
|
45
|
+
klass.extend(ClassMethods)
|
46
|
+
klass.get_queue
|
47
|
+
end
|
48
|
+
|
49
|
+
module ClassMethods
|
50
|
+
def perform_async(*params)
|
51
|
+
get_queue.push(self.name, params, 0, queue)
|
52
|
+
end
|
53
|
+
|
54
|
+
def perform_at(time, *params)
|
55
|
+
delay = time - Time.now.to_i
|
56
|
+
get_queue.push(self.name, params, delay, queue)
|
57
|
+
end
|
58
|
+
|
59
|
+
def perfrom_in(delay, *params)
|
60
|
+
get_queue.push(self.name, params, delay, queue)
|
61
|
+
end
|
62
|
+
|
63
|
+
def options
|
64
|
+
@@options ||= {}
|
65
|
+
end
|
66
|
+
|
67
|
+
def options=(options)
|
68
|
+
@@options = options
|
69
|
+
end
|
70
|
+
|
71
|
+
def queue
|
72
|
+
@@queue_name ||= "default"
|
73
|
+
end
|
74
|
+
|
75
|
+
def queue=(queue_name)
|
76
|
+
@@queue_name = queue_name.to_s
|
77
|
+
end
|
78
|
+
|
79
|
+
def get_queue
|
80
|
+
Litejobqueue.queue(options)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
# frozen_stringe_literal: true
|
2
|
+
|
3
|
+
require 'oj'
|
4
|
+
require 'yaml'
|
5
|
+
require_relative './litequeue'
|
6
|
+
|
7
|
+
##
|
8
|
+
#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.
|
9
|
+
#
|
10
|
+
#One of the main benefits of Litejobqueue is that it is very low on resources, making it an ideal choice for applications that need to manage a large number of jobs without incurring #high resource costs. In addition, because it is built on SQLite, it is easy to use and does not require any additional configuration or setup.
|
11
|
+
#
|
12
|
+
#Litejobqueue also integrates well with various I/O frameworks like Async and Polyphony, making it a great choice for Ruby applications that use these frameworks. It provides a #simple and easy-to-use API for adding jobs to the queue and for processing them.
|
13
|
+
#
|
14
|
+
#Overall, LiteJobQueue is an excellent choice for Ruby applications that require a lightweight, embedded job queueing and processing system that is fast, efficient, and easy to use.
|
15
|
+
class Litejobqueue
|
16
|
+
|
17
|
+
# the default options for the job queue
|
18
|
+
# can be overriden by passing new options in a hash
|
19
|
+
# to Litejobqueue.new, it will also be then passed to the underlying Litequeue object
|
20
|
+
# config_path: "./litejob.yml" -> were to find the configuration file (if any)
|
21
|
+
# path: "./queue.db"
|
22
|
+
# mmap_size: 128 * 1024 * 1024 -> 128MB to be held in memory
|
23
|
+
# sync: 1 -> sync only when checkpointing
|
24
|
+
# queues: [["default", 1, "spawn"]] -> an array of queues to process
|
25
|
+
# workers: 1 -> number of job processing workers
|
26
|
+
# sleep_intervals: [0.001, 0.005, 0.025, 0.125, 0.625, 3.125] -> sleep intervals for workers
|
27
|
+
# queues will be processed according to priority, such that if the queues are as such
|
28
|
+
# queues: [["default", 1, "spawn"], ["urgent", 10]]
|
29
|
+
# it means that roughly, if the queues are full, for each 10 urgent jobs, 1 default job will be processed
|
30
|
+
# the priority value is mandatory. The optional "spawn" parameter tells the job workers to spawn a separate execution context (thread or fiber, based on environment) for each job.
|
31
|
+
# This can be particularly useful for long running, IO bound jobs. It is not recommended though for threaded environments, as it can result in creating many threads that may consudme a lot of memory.
|
32
|
+
DEFAULT_OPTIONS = {
|
33
|
+
config_path: "./litejob.yml",
|
34
|
+
path: "./queue.db",
|
35
|
+
queues: [["default", 1, "spawn"]],
|
36
|
+
workers: 1,
|
37
|
+
sleep_intervals: [0.001, 0.005, 0.025, 0.125, 0.625, 3.125]
|
38
|
+
}
|
39
|
+
|
40
|
+
@@queue = nil
|
41
|
+
|
42
|
+
# a method that returns a single instance of the job queue
|
43
|
+
# for use by Litejob
|
44
|
+
def self.queue(options = {})
|
45
|
+
@@queue ||= Litesupport.synchronize{self.new(options)}
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.new(options = {})
|
49
|
+
return @@queue if @@queue
|
50
|
+
@@queue = allocate
|
51
|
+
@@queue.send(:initialize, options)
|
52
|
+
@@queue
|
53
|
+
end
|
54
|
+
|
55
|
+
# create new queue instance (only once instance will be created in the process)
|
56
|
+
# jobqueue = Litejobqueue.new
|
57
|
+
#
|
58
|
+
def initialize(options = {})
|
59
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
60
|
+
@worker_sleep_index = 0
|
61
|
+
config = YAML.load_file(@options[:config_path]) rescue {} #an empty hash won't hurt
|
62
|
+
config.each_key do |k| # symbolize keys
|
63
|
+
config[k.to_sym] = config[k]
|
64
|
+
config.delete k
|
65
|
+
end
|
66
|
+
@options.merge!(config)
|
67
|
+
@queue = Litequeue.new(@options) # create a new queue object
|
68
|
+
# group and order queues according to their priority
|
69
|
+
pgroups = {}
|
70
|
+
@options[:queues].each do |q|
|
71
|
+
pgroups[q[1]] = [] unless pgroups[q[1]]
|
72
|
+
pgroups[q[1]] << [q[0], q[2] == "spawn"]
|
73
|
+
end
|
74
|
+
@queues = pgroups.keys.sort.reverse.collect{|p| [p, pgroups[p]]}
|
75
|
+
@workers = @options[:workers].times.collect{create_worker}
|
76
|
+
end
|
77
|
+
|
78
|
+
# push a job to the queue
|
79
|
+
# class EasyJob
|
80
|
+
# def perform(any, number, of_params)
|
81
|
+
# # do anything
|
82
|
+
# end
|
83
|
+
# end
|
84
|
+
# jobqueue = Litejobqueue.new
|
85
|
+
# jobqueue.push(EasyJob, params) # the job will be performed asynchronously
|
86
|
+
def push(jobclass, params, delay=0, queue=nil)
|
87
|
+
payload = Oj.dump([jobclass, params])
|
88
|
+
@queue.push(payload, delay, queue)
|
89
|
+
end
|
90
|
+
|
91
|
+
# delete a job from the job queue
|
92
|
+
# class EasyJob
|
93
|
+
# def perform(any, number, of_params)
|
94
|
+
# # do anything
|
95
|
+
# end
|
96
|
+
# end
|
97
|
+
# jobqueue = Litejobqueue.new
|
98
|
+
# id = jobqueue.push(EasyJob, params, 10) # queue for processing in 10 seconds
|
99
|
+
# jobqueue.delete(id)
|
100
|
+
def delete(id)
|
101
|
+
job = @queue.delete(id)
|
102
|
+
Oj.load(job) if job
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
# optionally run a job in its own context
|
108
|
+
def schedule(spawn = false, &block)
|
109
|
+
if spawn
|
110
|
+
Litesupport.spawn &block
|
111
|
+
else
|
112
|
+
yield
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# create a worker according to environment
|
117
|
+
def create_worker
|
118
|
+
Litesupport.spawn do
|
119
|
+
Litesupport.switch
|
120
|
+
loop do
|
121
|
+
processed = 0
|
122
|
+
@queues.each do |level| # iterate through the levels
|
123
|
+
level[1].each do |q| # iterate through the queues in the level
|
124
|
+
index = 0
|
125
|
+
max = level[0]
|
126
|
+
while index < max && payload = @queue.pop(q[0])
|
127
|
+
processed += 1
|
128
|
+
index += 1
|
129
|
+
begin
|
130
|
+
id, job = payload[0], payload[1]
|
131
|
+
job = Oj.load(job)
|
132
|
+
klass = eval(job[0])
|
133
|
+
schedule(q[1]) do # run the job in a new context
|
134
|
+
begin
|
135
|
+
klass.new.perform(*job[1])
|
136
|
+
rescue Exception => e
|
137
|
+
puts e
|
138
|
+
puts e.message
|
139
|
+
puts e.backtrace
|
140
|
+
end
|
141
|
+
end
|
142
|
+
rescue Exception => e
|
143
|
+
puts e
|
144
|
+
puts e.message
|
145
|
+
puts e.backtrace
|
146
|
+
end
|
147
|
+
Litesupport.switch #give other context a chance to run here
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
if processed == 0
|
152
|
+
sleep @options[:sleep_intervals][@worker_sleep_index]
|
153
|
+
@worker_sleep_index += 1 if @worker_sleep_index < @options[:sleep_intervals].length - 1
|
154
|
+
else
|
155
|
+
@worker_sleep_index = 0 # reset the index
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_stringe_literal: true
|
2
|
+
|
3
|
+
# all components should require the support module
|
4
|
+
require_relative 'litesupport'
|
5
|
+
|
6
|
+
##
|
7
|
+
#Litequeue is a simple queueing system for Ruby applications that allows you to push and pop values from a queue. It provides a straightforward API for creating and managing named queues, and for adding and removing values from those queues. Additionally, it offers options for scheduling pops at a certain time in the future, which can be useful for delaying processing until a later time.
|
8
|
+
#
|
9
|
+
#Litequeue is built on top of SQLite, which makes it very fast and efficient, even when handling large volumes of data. This lightweight and easy-to-use queueing system serves as a good foundation for building more advanced job processing frameworks that require basic queuing capabilities.
|
10
|
+
#
|
11
|
+
|
12
|
+
class Litequeue
|
13
|
+
|
14
|
+
# the default options for the queue
|
15
|
+
# can be overriden by passing new options in a hash
|
16
|
+
# to Litequeue.new
|
17
|
+
# path: "./queue.db"
|
18
|
+
# mmap_size: 128 * 1024 * 1024 -> 128MB to be held in memory
|
19
|
+
# sync: 1 -> sync only when checkpointing
|
20
|
+
|
21
|
+
DEFAULT_OPTIONS = {
|
22
|
+
path: "./queue.db",
|
23
|
+
mmap_size: 32 * 1024,
|
24
|
+
sync: 1
|
25
|
+
}
|
26
|
+
|
27
|
+
# create a new instance of the litequeue object
|
28
|
+
# accepts an optional options hash which will be merged with the DEFAULT_OPTIONS
|
29
|
+
# queue = Litequeue.new
|
30
|
+
# queue.push("somevalue", 2) # the value will be ready to pop in 2 seconds
|
31
|
+
# queue.pop # => nil
|
32
|
+
# sleep 2
|
33
|
+
# queue.pop # => "somevalue"
|
34
|
+
|
35
|
+
def initialize(options = {})
|
36
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
37
|
+
@queue = create_db #(@options[:path])
|
38
|
+
prepare
|
39
|
+
end
|
40
|
+
|
41
|
+
# push an item to the queue, optionally specifying the queue name (defaults to default) and after how many seconds it should be ready to pop (defaults to zero)
|
42
|
+
# a unique job id is returned from this method, can be used later to delete it before it fires. You can push string, integer, float, true, false or nil values
|
43
|
+
#
|
44
|
+
def push(value, delay=0, queue='default')
|
45
|
+
result = @push.execute!(queue, delay, value)[0]
|
46
|
+
return result[0] if result
|
47
|
+
end
|
48
|
+
|
49
|
+
alias_method :"<<", :push
|
50
|
+
|
51
|
+
# pop an item from the queue, optionally with a specific queue name (default queue name is 'default')
|
52
|
+
def pop(queue='default')
|
53
|
+
result = nil
|
54
|
+
Litesupport.synchronize do
|
55
|
+
result = @pop.execute!(queue)[0]
|
56
|
+
end
|
57
|
+
result
|
58
|
+
end
|
59
|
+
|
60
|
+
# delete an item from the queue
|
61
|
+
# queue = Litequeue.new
|
62
|
+
# id = queue.push("somevalue")
|
63
|
+
# queue.delete(id) # => "somevalue"
|
64
|
+
# queue.pop # => nil
|
65
|
+
def delete(id, queue='default')
|
66
|
+
fire_at, id = id.split("_")
|
67
|
+
result = @deleter.execute!(queue, fire_at.to_i, id)[0]
|
68
|
+
end
|
69
|
+
|
70
|
+
# deletes all the entries in all queues, or if a queue name is given, deletes all entries in that specific queue
|
71
|
+
def clear(queue=nil)
|
72
|
+
@queue.execute("DELETE FROM _ul_queue_ WHERE iif(?, queue = ?, 1)", queue)
|
73
|
+
end
|
74
|
+
|
75
|
+
# returns a count of entries in all queues, or if a queue name is given, reutrns the count of entries in that queue
|
76
|
+
def count(queue=nil)
|
77
|
+
@queue.get_first_value("SELECT count(*) FROM _ul_queue_ WHERE iif(?, queue = ?, 1)", queue)
|
78
|
+
end
|
79
|
+
|
80
|
+
# return the size of the queue file on disk
|
81
|
+
def size
|
82
|
+
@queue.get_first_value("SELECT size.page_size * count.page_count FROM pragma_page_size() AS size, pragma_page_count() AS count")
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def create_db
|
88
|
+
db = Litesupport.create_db(@options[:path])
|
89
|
+
db.synchronous = @options[:sync]
|
90
|
+
db.wal_autocheckpoint = 10000
|
91
|
+
db.mmap_size = @options[:mmap_size]
|
92
|
+
db.execute("CREATE TABLE IF NOT EXISTS _ul_queue_(queue TEXT DEFAULT('default') NOT NULL ON CONFLICT REPLACE, fire_at INTEGER DEFAULT(unixepoch()) NOT NULL ON CONFLICT REPLACE, id TEXT DEFAULT(hex(randomblob(8)) || (strftime('%f') * 100)) NOT NULL ON CONFLICT REPLACE, value TEXT, created_at INTEGER DEFAULT(unixepoch()) NOT NULL ON CONFLICT REPLACE, PRIMARY KEY(queue, fire_at ASC, id) ) WITHOUT ROWID")
|
93
|
+
db
|
94
|
+
end
|
95
|
+
|
96
|
+
def prepare
|
97
|
+
@push = @queue.prepare("INSERT INTO _ul_queue_(queue, fire_at, value) VALUES ($1, (strftime('%s') + $2), $3) RETURNING fire_at || '-' || id")
|
98
|
+
@pop = @queue.prepare("DELETE FROM _ul_queue_ WHERE (queue, fire_at, id) = (SELECT queue, min(fire_at), id FROM _ul_queue_ WHERE queue = ifnull($1, 'default') AND fire_at <= (unixepoch()) limit 1) RETURNING fire_at || '-' || id, value")
|
99
|
+
@deleter = @queue.prepare("DELETE FROM _ul_queue_ WHERE queue = ifnull($1, 'default') AND fire_at = $2 AND id = $3 RETURNING value")
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'sqlite3'
|
2
|
+
|
3
|
+
module Litesupport
|
4
|
+
|
5
|
+
class Error < StandardError; end
|
6
|
+
|
7
|
+
# cache the environment we are running in
|
8
|
+
# it is an error to change the environment for a process
|
9
|
+
# or for a child forked from that process
|
10
|
+
def self.environment
|
11
|
+
@env ||= detect_environment
|
12
|
+
end
|
13
|
+
|
14
|
+
# identify which environment we are running in
|
15
|
+
# we currently support :fiber, :polyphony, :iodine & :threaded
|
16
|
+
# in the future we might want to expand to other environments
|
17
|
+
def self.detect_environment
|
18
|
+
return :fiber if Fiber.scheduler
|
19
|
+
return :polyphony if defined? Polyphony
|
20
|
+
return :iodine if defined? Iodine
|
21
|
+
return :threaded # fall back for all other environments
|
22
|
+
end
|
23
|
+
|
24
|
+
# spawn a new execution context
|
25
|
+
def self.spawn(&block)
|
26
|
+
if self.environment == :fiber
|
27
|
+
Fiber.schedule(&block)
|
28
|
+
elsif self.environment == :polyphony
|
29
|
+
spin(&block)
|
30
|
+
elsif self.environment == :threaded or self.environment == :iodine
|
31
|
+
Thread.new(&block)
|
32
|
+
end
|
33
|
+
# we should never reach here
|
34
|
+
end
|
35
|
+
|
36
|
+
# switch the execution context to allow others to run
|
37
|
+
def self.switch
|
38
|
+
if self.environment == :fiber
|
39
|
+
Fiber.scheduler.yield
|
40
|
+
elsif self.environment == :polyphony
|
41
|
+
Fiber.current.schedule
|
42
|
+
Thread.current.switch_fiber
|
43
|
+
else
|
44
|
+
# do nothing in case of thread, switching will auto-happen
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# mutex initialization
|
49
|
+
def self.mutex
|
50
|
+
# a single mutex per process (is that ok?)
|
51
|
+
@@mutex ||= Mutex.new
|
52
|
+
end
|
53
|
+
|
54
|
+
# bold assumption, we will only synchronize threaded code
|
55
|
+
# if some code explicitly wants to synchronize a fiber
|
56
|
+
# they must send (true) as a parameter to this method
|
57
|
+
# else it is a no-op for fibers
|
58
|
+
def self.synchronize(fiber_sync = false, &block)
|
59
|
+
if self.environment == :fiber or self.environment == :polyphony
|
60
|
+
yield # do nothing, just run the block as is
|
61
|
+
else
|
62
|
+
self.mutex.synchronize(&block)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# common db object options
|
67
|
+
def self.create_db(path)
|
68
|
+
db = SQLite3::Database.new(path)
|
69
|
+
db.busy_handler{ sleep 0.001 }
|
70
|
+
db.journal_mode = "WAL"
|
71
|
+
db
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
data/lib/litestack.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# load core classes
|
4
|
+
#require_relative "./version"
|
5
|
+
require_relative "litestack/litesupport"
|
6
|
+
#require_relative "litedb"
|
7
|
+
require_relative "litestack/litecache"
|
8
|
+
require_relative "litestack/litejob"
|
9
|
+
|
10
|
+
# conditionally load integration with other libraries
|
11
|
+
#require_relative "../sequel/adapters/litedb" if defined? Sequel
|
12
|
+
#require_relative "../active_record/connection_adapters/litedb_adapter" if defined? ActiveRecord
|
13
|
+
require_relative "active_support/cache/litecache" if defined? ActiveSupport
|
14
|
+
require_relative "active_job/queue_adapters/litejob_adapter" if defined? ActiveJob
|
15
|
+
#require_relative "../railties/rails/commands/dbconsole" if defined? Rails
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Rails
|
2
|
+
class DBConsole
|
3
|
+
|
4
|
+
def start
|
5
|
+
ENV["RAILS_ENV"] ||= @options[:environment] || environment
|
6
|
+
config = db_config.configuration_hash
|
7
|
+
|
8
|
+
case db_config.adapter
|
9
|
+
when /^(jdbc)?mysql/
|
10
|
+
args = {
|
11
|
+
host: "--host",
|
12
|
+
port: "--port",
|
13
|
+
socket: "--socket",
|
14
|
+
username: "--user",
|
15
|
+
encoding: "--default-character-set",
|
16
|
+
sslca: "--ssl-ca",
|
17
|
+
sslcert: "--ssl-cert",
|
18
|
+
sslcapath: "--ssl-capath",
|
19
|
+
sslcipher: "--ssl-cipher",
|
20
|
+
sslkey: "--ssl-key"
|
21
|
+
}.filter_map { |opt, arg| "#{arg}=#{config[opt]}" if config[opt] }
|
22
|
+
|
23
|
+
if config[:password] && @options[:include_password]
|
24
|
+
args << "--password=#{config[:password]}"
|
25
|
+
elsif config[:password] && !config[:password].to_s.empty?
|
26
|
+
args << "-p"
|
27
|
+
end
|
28
|
+
|
29
|
+
args << db_config.database
|
30
|
+
|
31
|
+
find_cmd_and_exec(["mysql", "mysql5"], *args)
|
32
|
+
|
33
|
+
when /^postgres|^postgis/
|
34
|
+
ENV["PGUSER"] = config[:username] if config[:username]
|
35
|
+
ENV["PGHOST"] = config[:host] if config[:host]
|
36
|
+
ENV["PGPORT"] = config[:port].to_s if config[:port]
|
37
|
+
ENV["PGPASSWORD"] = config[:password].to_s if config[:password] && @options[:include_password]
|
38
|
+
ENV["PGSSLMODE"] = config[:sslmode].to_s if config[:sslmode]
|
39
|
+
ENV["PGSSLCERT"] = config[:sslcert].to_s if config[:sslcert]
|
40
|
+
ENV["PGSSLKEY"] = config[:sslkey].to_s if config[:sslkey]
|
41
|
+
ENV["PGSSLROOTCERT"] = config[:sslrootcert].to_s if config[:sslrootcert]
|
42
|
+
find_cmd_and_exec("psql", db_config.database)
|
43
|
+
|
44
|
+
when "sqlite3", "litedb"
|
45
|
+
args = []
|
46
|
+
|
47
|
+
args << "-#{@options[:mode]}" if @options[:mode]
|
48
|
+
args << "-header" if @options[:header]
|
49
|
+
args << File.expand_path(db_config.database, Rails.respond_to?(:root) ? Rails.root : nil)
|
50
|
+
|
51
|
+
find_cmd_and_exec("sqlite3", *args)
|
52
|
+
|
53
|
+
|
54
|
+
when "oracle", "oracle_enhanced"
|
55
|
+
logon = ""
|
56
|
+
|
57
|
+
if config[:username]
|
58
|
+
logon = config[:username].dup
|
59
|
+
logon << "/#{config[:password]}" if config[:password] && @options[:include_password]
|
60
|
+
logon << "@#{db_config.database}" if db_config.database
|
61
|
+
end
|
62
|
+
|
63
|
+
find_cmd_and_exec("sqlplus", logon)
|
64
|
+
|
65
|
+
when "sqlserver"
|
66
|
+
args = []
|
67
|
+
|
68
|
+
args += ["-d", "#{db_config.database}"] if db_config.database
|
69
|
+
args += ["-U", "#{config[:username]}"] if config[:username]
|
70
|
+
args += ["-P", "#{config[:password]}"] if config[:password]
|
71
|
+
|
72
|
+
if config[:host]
|
73
|
+
host_arg = +"tcp:#{config[:host]}"
|
74
|
+
host_arg << ",#{config[:port]}" if config[:port]
|
75
|
+
args += ["-S", host_arg]
|
76
|
+
end
|
77
|
+
|
78
|
+
find_cmd_and_exec("sqlcmd", *args)
|
79
|
+
|
80
|
+
else
|
81
|
+
abort "Unknown command-line client for #{db_config.database}."
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require_relative '../../litestack/litedb'
|
2
|
+
require 'sequel'
|
3
|
+
require 'sequel/adapters/sqlite'
|
4
|
+
|
5
|
+
module Sequel
|
6
|
+
module Litedb
|
7
|
+
include SQLite
|
8
|
+
|
9
|
+
LITEDB_TYPES = SQLITE_TYPES
|
10
|
+
|
11
|
+
class Database < Sequel::SQLite::Database
|
12
|
+
|
13
|
+
set_adapter_scheme :litedb
|
14
|
+
|
15
|
+
|
16
|
+
def connect(server)
|
17
|
+
opts = server_opts(server)
|
18
|
+
opts[:database] = ':memory:' if blank_object?(opts[:database])
|
19
|
+
sqlite3_opts = {}
|
20
|
+
sqlite3_opts[:readonly] = typecast_value_boolean(opts[:readonly]) if opts.has_key?(:readonly)
|
21
|
+
db = ::Litedb.new(opts[:database].to_s, sqlite3_opts)
|
22
|
+
|
23
|
+
if sqlite_version >= 104
|
24
|
+
db.extended_result_codes = true
|
25
|
+
end
|
26
|
+
|
27
|
+
connection_pragmas.each{|s| log_connection_yield(s, db){db.execute_batch(s)}}
|
28
|
+
|
29
|
+
class << db
|
30
|
+
attr_reader :prepared_statements
|
31
|
+
end
|
32
|
+
db.instance_variable_set(:@prepared_statements, {})
|
33
|
+
|
34
|
+
db
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
class Dataset < Sequel::SQLite::Dataset
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|