rails-sqlite-extras 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a885ac19f93d301c20fa64d0a49f1f742ec5e99fdc2c1640560a0292e6f3d69d
4
- data.tar.gz: 8326db26cf1606a796aec10edd886d424cc85a8e1e582aa53ccd333d32008997
3
+ metadata.gz: e6d98e686e16d43b33d634d912d20a24bbe265a212ca1a634a497b577d3f13bf
4
+ data.tar.gz: 5f8e86e60d4e9ac302edf9f7857293c242d03c5622a852b30bd038e20aaa6adf
5
5
  SHA512:
6
- metadata.gz: 853ba689f98e3bd9ea863a3ed84250e0e6e483847d3e3f71494c252eb6b97759aa62a7c6de233b71ad577ac3d62d6303e52b92243961a5032ead192a03702eca
7
- data.tar.gz: b740b5b22380c5e229f98c8bb23c4bd0855e0c6bc43c39d004a75dc488b3a356b49858583444f945aece0174d251880e5bd4989956dbaea7642db526bb60cbad
6
+ metadata.gz: 1e2a85241730e526e6fb4926786904b825a797aa56a7354ad6a095f0b2e7577da1a2e7539f84985b61c38fdeb62f0f30f18df5f7ec52781b5dbf20ce71d6a6e4
7
+ data.tar.gz: f85072769f7c50592258f87b71fd2ef25901cdf2d310bcc4b3bf40d6174639102b4869dc5d4d06d3a3c63881f94125a511e0d9aeaa30fc2f90df2c2737362de2
@@ -12,7 +12,7 @@ jobs:
12
12
  strategy:
13
13
  fail-fast: false
14
14
  matrix:
15
- ruby-version: ['3.3', '3.2', '3.1', '3.0', '2.7', '2.6']
15
+ ruby-version: ['3.3', '3.2', '3.1']
16
16
  steps:
17
17
  - uses: actions/checkout@v4
18
18
  - name: Set up Ruby ${{ matrix.ruby-version }}
@@ -24,6 +24,8 @@ jobs:
24
24
  gem install bundler -v 2.4.22
25
25
  sudo apt-get update --allow-releaseinfo-change
26
26
  bundle config set --local path 'vendor/bundle'
27
+ bundle config set force_ruby_platform true
28
+ bundle config set build.sqlite3 "--with-sqlite-cflags='-DSQLITE_ENABLE_DBSTAT_VTAB=1'"
27
29
  bundle install
28
30
  sleep 2
29
31
  - name: Run tests
data/.gitignore CHANGED
@@ -1,4 +1,5 @@
1
1
  Gemfile.lock
2
+ .bundle/
2
3
  .ruby-version
3
4
  pkg/
4
5
  *.gem
data/README.md CHANGED
@@ -1,3 +1,140 @@
1
1
  # Rails Sqlite Extras [![Gem Version](https://badge.fury.io/rb/rails-sqlite-extras.svg)](https://badge.fury.io/rb/rails-sqlite-extras) [![GH Actions](https://github.com/pawurb/rails-sqlite-extras/actions/workflows/ci.yml/badge.svg)](https://github.com/pawurb/rails-sqlite-extras/actions)
2
2
 
3
- WIP
3
+ Copy/paste of [ecto_sqlite3_extras](https://github.com/orsinium-labs/ecto_sqlite3_extras). Helper queries available in Ruby and rake tasks providing insights into the Sqlite database.
4
+
5
+ ## Installation
6
+
7
+ Some queries use `dbstat` table which has to be enabled via a `SQLITE_ENABLE_DBSTAT_VTAB` compile flag:
8
+
9
+ ```bash
10
+ bundle config set force_ruby_platform true
11
+ bundle config set build.sqlite3 "--with-sqlite-cflags='-DSQLITE_ENABLE_DBSTAT_VTAB=1'"
12
+ ```
13
+
14
+ `Gemfile`
15
+ ```bash
16
+ gem 'rails-sqlite-extras'
17
+ ```
18
+
19
+ ## Available queries
20
+
21
+ ### `total_size`
22
+
23
+ ```bash
24
+ rake sqlite_extras:total_size
25
+ ```
26
+
27
+ ```ruby
28
+ RailsSqliteExtras.total_size
29
+ ```
30
+
31
+ The total size of all tables and indices. It's a summary table, it has only 2 columns: `name` and `value`. Rows:
32
+
33
+ - `cells`: The number of cells in the DB. Each value stored in the DB is represented as at least one cell. So, the number of cells correlates with the number of records in the DB.
34
+ - `payload_size`: How much space the actual useful payload takes in the DB.
35
+ - `unused_size`: How much space in the DB is reserved, not used yet, and can be used later to store more data. This is a surplus that occurs because SQLite allocates space for data in chunks ("pages").
36
+ - `vacuum_size`: How much space is unused and cannot be used for future data. You can run [VACUUM](https://www.sqlite.org/lang_vacuum.html) command to reduce it.
37
+ - `page_size`: The total space occupied by all pages. Each page is a single chunk of space allocated by SQLite. This number is the sum of `payload_size`, `unused_size`, and `vacuum_size`.
38
+ - `pages`: The total number of pages.
39
+ - `pages: leaf`: The pages that store the actual data. Read [SQLite Internals: Pages & B-trees](https://fly.io/blog/sqlite-internals-btree/) to learn more.
40
+ - `pages: internal`: The pages that store ranges for leaf pages for a faster lookup. Sometimes also called "interior pages".
41
+ - `pages: overflow`: The pages that store chunks of big data that don't fit in a single leaf page.
42
+ - `pages: table`: The pages used for storing data for tables.
43
+ - `pages: index`: The pages used for storing indices.
44
+
45
+ ### `table_size`
46
+
47
+ ```bash
48
+ rake sqlite_extras:table_size
49
+ ```
50
+
51
+ ```ruby
52
+ RailsSqliteExtras.table_size
53
+ ```
54
+
55
+ Information about the space used (and unused) by all tables. Based on the [dbstat](https://www.sqlite.org/dbstat.html) virtual table.
56
+
57
+ - `name`: The table name.
58
+ - `payload_size`.
59
+ - `unused_size`.
60
+ - `vacuum_size`.
61
+ - `page_size`.
62
+ - `cells`.
63
+ - `pages`.
64
+ - `max_payload_size`: The size of the biggest payload in the table.
65
+
66
+ ### `index_size`
67
+
68
+ ```bash
69
+ rake sqlite_extras:index_size
70
+ ```
71
+
72
+ ```ruby
73
+ RailsSqliteExtras.index_size
74
+ ```
75
+
76
+ Size of all indices.
77
+
78
+ - `name`: The index name.
79
+ - `table_name`: The table where the index is defined.
80
+ - `column_name`: The name of the column being indexed. This column is NULL if the column is the rowid or an expression.
81
+ - `payload_size`.
82
+ - `unused_size`.
83
+ - `page_size`.
84
+ - `cells`.
85
+ - `pages`.
86
+ - `max_payload_size`.
87
+
88
+ ### `sequence_number`
89
+
90
+ ```bash
91
+ rake sqlite_extras:sequence_number
92
+ ```
93
+
94
+ ```ruby
95
+ RailsSqliteExtras.sequence_number
96
+ ```
97
+
98
+ Sequence numbers of autoincrement columns. Generated based on the [sqlite_sequence](https://renenyffenegger.ch/notes/development/databases/SQLite/internals/schema-objects/sqlite_sequence) table. The query will fail if there are no autoincrement columns in the DB yet.
99
+
100
+ - `table_name`.
101
+ - `sequence_number`.
102
+
103
+ ### `pragma`
104
+
105
+ ```bash
106
+ rake sqlite_extras:pragma
107
+ ```
108
+
109
+ ```ruby
110
+ RailsSqliteExtras.pragma
111
+ ```
112
+
113
+ List values of PRAGMAs (settings). Only includes the ones that have an integer or a boolean value. For brevity, the ones with the `0` (`false`) value are excluded from the output (based on the observation that this is the default value for most of the PRAGMAs). Check out the SQLite documentation to learn more about what each PRAGMA means: [PRAGMA Statements](https://www.sqlite.org/pragma.html).
114
+
115
+ - `name`: the name of the PRAGMA as listed in the SQLite documentation.
116
+ - `value`: the value of the PRAGMA. The `true` value is converted into `1` (and `false` is simply excluded).
117
+
118
+ ### `compile_options`
119
+
120
+ ```bash
121
+ rake sqlite_extras:compile_options
122
+ ```
123
+
124
+ ```ruby
125
+ RailsSqliteExtras.compile_options
126
+ ```
127
+
128
+ List the [compile-time options](https://www.sqlite.org/compile.html) used when building SQLite, one option per row. The "SQLITE_" prefix is omitted from the returned option names. See [exqlite docs](https://github.com/elixir-sqlite/exqlite#defining-extra-compile-flags) on how to change these options.
129
+
130
+ ### `integrity_check`
131
+
132
+ ```bash
133
+ rake sqlite_extras:integrity_check
134
+ ```
135
+
136
+ ```ruby
137
+ RailsSqliteExtras.integrity_check
138
+ ```
139
+
140
+ Run integrity checks on the database. Executes [PRAGMA integrity_check](https://www.sqlite.org/pragma.html#pragma_integrity_check) and returns the resulting messages.
@@ -1,7 +1,97 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsSqliteExtras
4
- def self.hello
5
- "there"
4
+ @@database_url = nil
5
+
6
+ QUERIES = %i(
7
+ compile_options
8
+ index_size
9
+ integrity_check
10
+ pragma
11
+ sequence_number
12
+ table_size
13
+ total_size
14
+ )
15
+
16
+ QUERIES.each do |query_name|
17
+ define_singleton_method query_name do |options = {}|
18
+ run_query(
19
+ query_name: query_name,
20
+ in_format: options.fetch(:in_format, :display_table),
21
+ )
22
+ end
23
+ end
24
+
25
+ def self.run_query(query_name:, in_format:)
26
+ sql = sql_for(query_name: query_name)
27
+
28
+ result = connection.execute(sql)
29
+
30
+ self.display_result(
31
+ result,
32
+ title: self.description_for(query_name: query_name),
33
+ in_format: in_format,
34
+ )
35
+ end
36
+
37
+ def self.display_result(result, title:, in_format:)
38
+ case in_format
39
+ when :array
40
+ result.values
41
+ when :hash
42
+ result.to_a
43
+ when :raw
44
+ result
45
+ when :display_table
46
+ headings = if result.count > 0
47
+ result[0].keys
48
+ else
49
+ ["No results"]
50
+ end
51
+
52
+ puts Terminal::Table.new(
53
+ title: title,
54
+ headings: headings,
55
+ rows: result.values,
56
+ )
57
+ else
58
+ raise "Invalid in_format option"
59
+ end
60
+ end
61
+
62
+ def self.sql_for(query_name:)
63
+ File.read(
64
+ sql_path_for(query_name: query_name)
65
+ )
66
+ end
67
+
68
+ def self.description_for(query_name:)
69
+ first_line = File.open(
70
+ sql_path_for(query_name: query_name)
71
+ ) { |f| f.readline }
72
+
73
+ first_line[/\/\*(.*?)\*\//m, 1].strip
74
+ end
75
+
76
+ def self.sql_path_for(query_name:)
77
+ File.join(File.dirname(__FILE__), "/rails_sqlite_extras/queries/#{query_name}.sql")
78
+ end
79
+
80
+ def self.connection
81
+ if (db_url = ENV["RAILS_SQLITE_EXTRAS_DATABASE_URL"])
82
+ ActiveRecord::Base.establish_connection(db_url).lease_connection
83
+ else
84
+ ActiveRecord::Base.connection
85
+ end
86
+ end
87
+
88
+ def self.database_url=(value)
89
+ @@database_url = value
90
+ end
91
+
92
+ def self.database_url
93
+ @@database_url || ENV["RUBY_PG_EXTRAS_DATABASE_URL"] || ENV.fetch("DATABASE_URL")
6
94
  end
7
95
  end
96
+
97
+ require "rails_sqlite_extras/railtie" if defined?(Rails)
@@ -0,0 +1,3 @@
1
+ /* List the compile-time options used when building SQLite. */
2
+
3
+ SELECT compile_options AS 'value' FROM pragma_compile_options;
@@ -0,0 +1,21 @@
1
+ /* Metadata of the indexes, descending by size. */
2
+
3
+ SELECT
4
+ d.name AS name,
5
+ s.tbl_name AS table_name,
6
+ i.name AS column_name,
7
+ d.payload AS payload_size,
8
+ d.unused AS unused_size,
9
+ d.pgsize AS page_size,
10
+ d.ncell AS cells,
11
+ d.pageno AS pages,
12
+ d.mx_payload AS max_payload_size
13
+ FROM
14
+ dbstat AS d,
15
+ sqlite_schema AS s,
16
+ pragma_index_info(d.name) AS i
17
+ WHERE
18
+ d.name = s.name
19
+ AND s.type = 'index'
20
+ AND d.aggregate = TRUE
21
+ ORDER BY page_size DESC;
@@ -0,0 +1,4 @@
1
+ /* Run integrity checks on the database. */
2
+
3
+ SELECT integrity_check as message
4
+ FROM pragma_integrity_check;
@@ -0,0 +1,35 @@
1
+ /* List values of PRAGMAs (settings). */
2
+
3
+ SELECT *
4
+ FROM (
5
+ SELECT 'analysis_limit' AS name, analysis_limit AS value FROM pragma_analysis_limit
6
+ UNION ALL SELECT 'application_id', * FROM pragma_application_id
7
+ UNION ALL SELECT 'auto_vacuum', * FROM pragma_auto_vacuum
8
+ UNION ALL SELECT 'automatic_index', * FROM pragma_automatic_index
9
+ UNION ALL SELECT 'busy_timeout', * FROM pragma_busy_timeout
10
+ UNION ALL SELECT 'cache_size', * FROM pragma_cache_size
11
+ UNION ALL SELECT 'cache_spill', * FROM pragma_cache_spill
12
+ UNION ALL SELECT 'cell_size_check', * FROM pragma_cell_size_check
13
+ UNION ALL SELECT 'checkpoint_fullfsync', * FROM pragma_checkpoint_fullfsync
14
+ UNION ALL SELECT 'data_version', * FROM pragma_data_version
15
+ UNION ALL SELECT 'defer_foreign_keys', * FROM pragma_defer_foreign_keys
16
+ UNION ALL SELECT 'freelist_count', * FROM pragma_freelist_count
17
+ UNION ALL SELECT 'fullfsync', * FROM pragma_fullfsync
18
+ UNION ALL SELECT 'hard_heap_limit', * FROM pragma_hard_heap_limit
19
+ UNION ALL SELECT 'ignore_check_constraints', * FROM pragma_ignore_check_constraints
20
+ UNION ALL SELECT 'journal_size_limit', * FROM pragma_journal_size_limit
21
+ UNION ALL SELECT 'legacy_alter_table', * FROM pragma_legacy_alter_table
22
+ UNION ALL SELECT 'max_page_count', * FROM pragma_max_page_count
23
+ UNION ALL SELECT 'page_size', * FROM pragma_page_size
24
+ UNION ALL SELECT 'query_only', * FROM pragma_query_only
25
+ UNION ALL SELECT 'read_uncommitted', * FROM pragma_read_uncommitted
26
+ UNION ALL SELECT 'recursive_triggers', * FROM pragma_recursive_triggers
27
+ UNION ALL SELECT 'reverse_unordered_selects', * FROM pragma_reverse_unordered_selects
28
+ UNION ALL SELECT 'soft_heap_limit', * FROM pragma_soft_heap_limit
29
+ UNION ALL SELECT 'synchronous', * FROM pragma_synchronous
30
+ UNION ALL SELECT 'temp_store', * FROM pragma_temp_store
31
+ UNION ALL SELECT 'threads', * FROM pragma_threads
32
+ UNION ALL SELECT 'trusted_schema', * FROM pragma_trusted_schema
33
+ UNION ALL SELECT 'user_version', * FROM pragma_user_version
34
+ )
35
+ WHERE value != 0
@@ -0,0 +1,5 @@
1
+ /* Sequence numbers of autoincrement columns. */
2
+
3
+ SELECT name AS table_name, seq AS sequence_number
4
+ FROM sqlite_sequence
5
+ ORDER BY sequence_number DESC
@@ -0,0 +1,15 @@
1
+ /* Metadata of the tables (excluding indexes), descending by size. */
2
+ SELECT
3
+ name,
4
+ payload AS payload_size,
5
+ unused as unused_size,
6
+ pgsize - payload - unused as vacuum_size,
7
+ pgsize as page_size,
8
+ ncell as cells,
9
+ pageno as pages,
10
+ mx_payload as max_payload_size
11
+ FROM dbstat
12
+ WHERE
13
+ name IN (SELECT name FROM sqlite_schema WHERE type='table')
14
+ AND aggregate=TRUE
15
+ ORDER BY page_size DESC;
@@ -0,0 +1,18 @@
1
+ /* The total size of all tables and indexes. */
2
+ SELECT 'pages' AS name, MAX(pageno) as value FROM dbstat
3
+ UNION ALL SELECT 'cells', SUM(ncell) FROM dbstat
4
+ UNION ALL SELECT 'payload_size', SUM(payload) FROM dbstat
5
+ UNION ALL SELECT 'unused_size', SUM(unused) FROM dbstat
6
+ UNION ALL SELECT 'vacuum_size', SUM(pgsize) - SUM(payload) - SUM(unused) FROM dbstat
7
+ UNION ALL SELECT 'page_size', SUM(pgsize) FROM dbstat
8
+
9
+ UNION ALL SELECT 'pages: leaf', COUNT(*)
10
+ FROM dbstat WHERE pagetype = 'leaf'
11
+ UNION ALL SELECT 'pages: internal', COUNT(*)
12
+ FROM dbstat WHERE pagetype = 'internal'
13
+ UNION ALL SELECT 'pages: overflow', COUNT(*)
14
+ FROM dbstat WHERE pagetype = 'overflow'
15
+ UNION ALL SELECT 'pages: table', COUNT(*)
16
+ FROM dbstat WHERE name IN (SELECT name FROM sqlite_schema WHERE type='table')
17
+ UNION ALL SELECT 'pages: index', COUNT(*)
18
+ FROM dbstat WHERE name IN (SELECT name FROM sqlite_schema WHERE type='index')
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RailsSqliteExtras::Railtie < Rails::Railtie
4
+ rake_tasks do
5
+ load "rails_sqlite_extras/tasks/all.rake"
6
+ end
7
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails-sqlite-extras"
4
+
5
+ namespace :sqlite_extras do
6
+ RailsSqliteExtras::QUERIES.each do |query_name|
7
+ desc RailsSqliteExtras.description_for(query_name: query_name)
8
+ task query_name.to_sym => :environment do
9
+ RailsSqliteExtras.public_send(query_name)
10
+ end
11
+ end
12
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsSqliteExtras
4
- VERSION = "0.0.1"
4
+ VERSION = "0.1.0"
5
5
  end
@@ -11,11 +11,12 @@ Gem::Specification.new do |s|
11
11
  s.summary = %q{ Rails Sqlite database insights. }
12
12
  s.description = %q{ Helper queries providing insights into the Sqlite database for Rails apps. }
13
13
  s.homepage = "http://github.com/pawurb/rails-sqlite-extras"
14
- s.files = `git ls-files`.split("\n")
14
+ s.files = `git ls-files`.split("\n").reject { |f| f.match?(/\.db$/) }
15
15
  s.test_files = s.files.grep(%r{^(spec)/})
16
16
  s.require_paths = ["lib"]
17
17
  s.license = "MIT"
18
18
  s.add_dependency "rails"
19
+ s.add_dependency "sqlite3"
19
20
  s.add_development_dependency "rake"
20
21
  s.add_development_dependency "rspec"
21
22
  s.add_development_dependency "rufo"
data/spec/smoke_spec.rb CHANGED
@@ -4,7 +4,24 @@ require "spec_helper"
4
4
  require "rails-sqlite-extras"
5
5
 
6
6
  describe RailsSqliteExtras do
7
- it "works" do
8
- expect(RailsSqliteExtras.hello).to eq("there")
7
+ RailsSqliteExtras::QUERIES.each do |query_name|
8
+ it "#{query_name} description can be read" do
9
+ expect do
10
+ RailsSqliteExtras.description_for(
11
+ query_name: query_name,
12
+ )
13
+ end.not_to raise_error
14
+ end
15
+ end
16
+
17
+ RailsSqliteExtras::QUERIES.reject { |q| q == :kill_all }.each do |query_name|
18
+ it "#{query_name} query can be executed" do
19
+ expect do
20
+ RailsSqliteExtras.run_query(
21
+ query_name: query_name,
22
+ in_format: :hash,
23
+ )
24
+ end.not_to raise_error
25
+ end
9
26
  end
10
27
  end
data/spec/spec_helper.rb CHANGED
@@ -7,6 +7,7 @@ require_relative "../lib/rails-sqlite-extras"
7
7
 
8
8
  RSpec.configure do |config|
9
9
  config.before :suite do
10
+ ENV["RAILS_SQLITE_EXTRAS_DATABASE_URL"] = "sqlite3://#{Dir.pwd}/test.db"
10
11
  end
11
12
 
12
13
  config.after :suite do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-sqlite-extras
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - pawurb
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-11-14 00:00:00.000000000 Z
11
+ date: 2024-12-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: sqlite3
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rake
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -81,6 +95,15 @@ files:
81
95
  - README.md
82
96
  - Rakefile
83
97
  - lib/rails-sqlite-extras.rb
98
+ - lib/rails_sqlite_extras/queries/compile_options.sql
99
+ - lib/rails_sqlite_extras/queries/index_size.sql
100
+ - lib/rails_sqlite_extras/queries/integrity_check.sql
101
+ - lib/rails_sqlite_extras/queries/pragma.sql
102
+ - lib/rails_sqlite_extras/queries/sequence_number.sql
103
+ - lib/rails_sqlite_extras/queries/table_size.sql
104
+ - lib/rails_sqlite_extras/queries/total_size.sql
105
+ - lib/rails_sqlite_extras/railtie.rb
106
+ - lib/rails_sqlite_extras/tasks/all.rake
84
107
  - lib/rails_sqlite_extras/version.rb
85
108
  - rails-sqlite-extras.gemspec
86
109
  - spec/smoke_spec.rb