Class: Trifle::Stats::Driver::Sqlite
- Inherits:
-
Object
- Object
- Trifle::Stats::Driver::Sqlite
- Includes:
- Mixins::Packer
- Defined in:
- lib/trifle/stats/driver/sqlite.rb
Overview
rubocop:disable Metrics/ClassLength
Instance Attribute Summary collapse
-
#client ⇒ Object
Returns the value of attribute client.
-
#ping_table_name ⇒ Object
Returns the value of attribute ping_table_name.
-
#separator ⇒ Object
readonly
Returns the value of attribute separator.
-
#table_name ⇒ Object
Returns the value of attribute table_name.
Class Method Summary collapse
- .normalize_joined_identifier(value) ⇒ Object
-
.setup!(client = SQLite3::Database.new('stats.db'), table_name: 'trifle_stats', joined_identifier: :full, ping_table_name: nil) ⇒ Object
rubocop:disable Layout/LineLength, Metrics/MethodLength.
Instance Method Summary collapse
- #description ⇒ Object
- #get(keys:) ⇒ Object
-
#get_all(identifiers:) ⇒ Object
rubocop:disable Metrics/AbcSize.
- #get_query(identifiers:) ⇒ Object
- #inc(keys:, values:, count: 1, tracking_key: nil) ⇒ Object
-
#inc_query(identifier:, data:) ⇒ Object
rubocop:disable Metrics/MethodLength.
-
#initialize(client = SQLite3::Database.new('stats.db'), table_name: 'trifle_stats', joined_identifier: :full, ping_table_name: nil, system_tracking: true) ⇒ Sqlite
constructor
rubocop:disable Layout/LineLength.
- #ping(key:, values:) ⇒ Object
- #ping_query(key:, at:, data:) ⇒ Object
- #scan(key:) ⇒ Object
- #scan_query(key:) ⇒ Object
- #set(keys:, values:, count: 1, tracking_key: nil) ⇒ Object
-
#set_query(identifier:, data:) ⇒ Object
rubocop:disable Metrics/MethodLength.
- #system_data_for(key:, count: 1, tracking_key: nil) ⇒ Object
- #system_identifier_for(key:) ⇒ Object
- #track_system_data(connection, key, count, tracking_key) ⇒ Object
Methods included from Mixins::Packer
Constructor Details
#initialize(client = SQLite3::Database.new('stats.db'), table_name: 'trifle_stats', joined_identifier: :full, ping_table_name: nil, system_tracking: true) ⇒ Sqlite
rubocop:disable Layout/LineLength
14 15 16 17 18 19 20 21 |
# File 'lib/trifle/stats/driver/sqlite.rb', line 14 def initialize(client = SQLite3::Database.new('stats.db'), table_name: 'trifle_stats', joined_identifier: :full, ping_table_name: nil, system_tracking: true) # rubocop:disable Layout/LineLength @client = client @table_name = table_name @ping_table_name = ping_table_name || "#{table_name}_ping" @joined_identifier = self.class.normalize_joined_identifier(joined_identifier) @system_tracking = system_tracking @separator = '::' end |
Instance Attribute Details
#client ⇒ Object
Returns the value of attribute client.
12 13 14 |
# File 'lib/trifle/stats/driver/sqlite.rb', line 12 def client @client end |
#ping_table_name ⇒ Object
Returns the value of attribute ping_table_name.
12 13 14 |
# File 'lib/trifle/stats/driver/sqlite.rb', line 12 def ping_table_name @ping_table_name end |
#separator ⇒ Object (readonly)
Returns the value of attribute separator.
50 51 52 |
# File 'lib/trifle/stats/driver/sqlite.rb', line 50 def separator @separator end |
#table_name ⇒ Object
Returns the value of attribute table_name.
12 13 14 |
# File 'lib/trifle/stats/driver/sqlite.rb', line 12 def table_name @table_name end |
Class Method Details
.normalize_joined_identifier(value) ⇒ Object
198 199 200 201 202 203 204 205 |
# File 'lib/trifle/stats/driver/sqlite.rb', line 198 def self.normalize_joined_identifier(value) case value when nil, :full, 'full', :partial, 'partial' value.nil? ? nil : value.to_sym else raise ArgumentError, 'joined_identifier must be nil, :full, "full", :partial, or "partial"' end end |
.setup!(client = SQLite3::Database.new('stats.db'), table_name: 'trifle_stats', joined_identifier: :full, ping_table_name: nil) ⇒ Object
rubocop:disable Layout/LineLength, Metrics/MethodLength
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
# File 'lib/trifle/stats/driver/sqlite.rb', line 23 def self.setup!(client = SQLite3::Database.new('stats.db'), table_name: 'trifle_stats', joined_identifier: :full, ping_table_name: nil) # rubocop:disable Layout/LineLength, Metrics/MethodLength ping_table_name ||= "#{table_name}_ping" identifier_mode = normalize_joined_identifier(joined_identifier) case identifier_mode when :full client.execute("CREATE TABLE #{table_name} (key varchar(255), data json);") client.execute("CREATE UNIQUE INDEX idx_#{table_name}_key ON #{table_name} (key);") when :partial client.execute("CREATE TABLE #{table_name} (key varchar(255) NOT NULL, at datetime NOT NULL, data json, PRIMARY KEY (key, at));") # rubocop:disable Layout/LineLength else client.execute("CREATE TABLE #{table_name} (key varchar(255) NOT NULL, granularity varchar(255) NOT NULL, at datetime NOT NULL, data json, PRIMARY KEY (key, granularity, at));") # rubocop:disable Layout/LineLength # Create ping table for separated mode only client.execute("CREATE TABLE #{ping_table_name} (key varchar(255) PRIMARY KEY, at datetime NOT NULL, data json);") # rubocop:disable Layout/LineLength end end |
Instance Method Details
#description ⇒ Object
41 42 43 44 45 46 47 48 |
# File 'lib/trifle/stats/driver/sqlite.rb', line 41 def description mode = if @joined_identifier == :full 'J' else @joined_identifier == :partial ? 'P' : 'S' end "#{self.class.name}(#{mode})" end |
#get(keys:) ⇒ Object
126 127 128 129 130 |
# File 'lib/trifle/stats/driver/sqlite.rb', line 126 def get(keys:) identifiers = keys.map { |key| identifier_for(key) } data = get_all(identifiers: identifiers) identifiers.map { |identifier| self.class.unpack(hash: data.fetch(identifier, {})) } end |
#get_all(identifiers:) ⇒ Object
rubocop:disable Metrics/AbcSize
132 133 134 135 136 137 138 139 140 141 142 143 144 |
# File 'lib/trifle/stats/driver/sqlite.rb', line 132 def get_all(identifiers:) # rubocop:disable Metrics/AbcSize query, params = get_query(identifiers: identifiers) results = client.execute(query, params).to_a sample = identifiers.first results.each_with_object(Hash.new({})) do |r, o| identifier = sample.each_with_index.to_h { |(k, _), i| [k, k == :at ? Time.iso8601(r[i]) : r[i]] } o[identifier] = JSON.parse(r.last) rescue JSON::ParserError nil end end |
#get_query(identifiers:) ⇒ Object
146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/trifle/stats/driver/sqlite.rb', line 146 def get_query(identifiers:) params = [] conditions = identifiers.map do |identifier| identifier.map { |k, v| build_field_condition(k, v, params) }.join(' AND ') end.join(' OR ') query = <<-SQL SELECT * FROM #{table_name} WHERE #{conditions}; SQL [query, params] end |
#inc(keys:, values:, count: 1, tracking_key: nil) ⇒ Object
62 63 64 65 66 67 68 69 70 71 |
# File 'lib/trifle/stats/driver/sqlite.rb', line 62 def inc(keys:, values:, count: 1, tracking_key: nil) data = self.class.pack(hash: values) client.transaction do |c| keys.each do |key| identifier = identifier_for(key) batch_data_operations(identifier: identifier, data: data, connection: c, operation: :inc) track_system_data(c, key, count, tracking_key) end end end |
#inc_query(identifier:, data:) ⇒ Object
rubocop:disable Metrics/MethodLength
73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/trifle/stats/driver/sqlite.rb', line 73 def inc_query(identifier:, data:) # rubocop:disable Metrics/MethodLength columns = identifier.keys.join(', ') placeholders = (['?'] * identifier.size).join(', ') expression = data.inject('data') do |o, (k, v)| path = json_path_for(k) "json_set(#{o}, '#{path}', IFNULL(json_extract(data, '#{path}'), 0) + #{numeric_value(k, v)})" end query = <<-SQL INSERT INTO #{table_name} (#{columns}, data) VALUES (#{placeholders}, json(?)) ON CONFLICT (#{columns}) DO UPDATE SET data = #{expression}; SQL [query, query_params(identifier) + [data.to_json]] end |
#ping(key:, values:) ⇒ Object
158 159 160 161 162 163 164 165 166 |
# File 'lib/trifle/stats/driver/sqlite.rb', line 158 def ping(key:, values:) return [] if @joined_identifier data = self.class.pack(hash: { data: values, at: key.at }) query, params = ping_query(key: key.key, at: key.at, data: data) client.transaction do |c| c.execute(query, params) # TODO: should use batch_data_operations end end |
#ping_query(key:, at:, data:) ⇒ Object
168 169 170 171 172 173 174 175 176 |
# File 'lib/trifle/stats/driver/sqlite.rb', line 168 def ping_query(key:, at:, data:) at_formatted = format_time_value(at) query = <<-SQL INSERT INTO #{ping_table_name} (key, at, data) VALUES (?, ?, json(?)) ON CONFLICT (key) DO UPDATE SET at = ?, data = json(?); SQL [query, [key.to_s, at_formatted, data.to_json, at_formatted, data.to_json]] end |
#scan(key:) ⇒ Object
178 179 180 181 182 183 184 185 186 187 188 189 |
# File 'lib/trifle/stats/driver/sqlite.rb', line 178 def scan(key:) return [] if @joined_identifier query, params = scan_query(key: key.key) result = client.execute(query, params).first return [] if result.nil? # SQLite returns columns in order: key, at, data [Time.iso8601(result[1]), self.class.unpack(hash: JSON.parse(result[2]))] rescue JSON::ParserError [] end |
#scan_query(key:) ⇒ Object
191 192 193 194 195 196 |
# File 'lib/trifle/stats/driver/sqlite.rb', line 191 def scan_query(key:) query = <<-SQL SELECT key, at, data FROM #{ping_table_name} WHERE key = ? ORDER BY at DESC LIMIT 1; SQL [query, [key.to_s]] end |
#set(keys:, values:, count: 1, tracking_key: nil) ⇒ Object
88 89 90 91 92 93 94 95 96 97 |
# File 'lib/trifle/stats/driver/sqlite.rb', line 88 def set(keys:, values:, count: 1, tracking_key: nil) data = self.class.pack(hash: values) client.transaction do |c| keys.each do |key| identifier = identifier_for(key) batch_data_operations(identifier: identifier, data: data, connection: c, operation: :set) track_system_data(c, key, count, tracking_key) end end end |
#set_query(identifier:, data:) ⇒ Object
rubocop:disable Metrics/MethodLength
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/trifle/stats/driver/sqlite.rb', line 110 def set_query(identifier:, data:) # rubocop:disable Metrics/MethodLength columns = identifier.keys.join(', ') placeholders = (['?'] * identifier.size).join(', ') params = query_params(identifier) + [data.to_json] expression = data.inject('data') do |o, (k, v)| params << v.to_json "json_set(#{o}, '#{json_path_for(k)}', json(?))" end query = <<-SQL INSERT INTO #{table_name} (#{columns}, data) VALUES (#{placeholders}, json(?)) ON CONFLICT (#{columns}) DO UPDATE SET data = #{expression}; SQL [query, params] end |
#system_data_for(key:, count: 1, tracking_key: nil) ⇒ Object
57 58 59 60 |
# File 'lib/trifle/stats/driver/sqlite.rb', line 57 def system_data_for(key:, count: 1, tracking_key: nil) tracking_key ||= key.key self.class.pack(hash: { count: count, keys: { tracking_key => count } }) end |
#system_identifier_for(key:) ⇒ Object
52 53 54 55 |
# File 'lib/trifle/stats/driver/sqlite.rb', line 52 def system_identifier_for(key:) key = Nocturnal::Key.new(key: '__system__key__', granularity: key.granularity, at: key.at) identifier_for(key) end |
#track_system_data(connection, key, count, tracking_key) ⇒ Object
99 100 101 102 103 104 105 106 107 108 |
# File 'lib/trifle/stats/driver/sqlite.rb', line 99 def track_system_data(connection, key, count, tracking_key) return unless @system_tracking batch_data_operations( identifier: system_identifier_for(key: key), data: system_data_for(key: key, count: count, tracking_key: tracking_key), connection: connection, operation: :inc ) end |