simple-sql 0.4.32 → 0.4.35

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 98414c53004214c032d2432a3e17f19ea0c32fa81d1e8ab15cbfb843ec48ec6c
4
- data.tar.gz: 0bc1b9bbbdba63fe6a91d750e23f47fd334779c5e6b501ef1f4d60e0131d5073
3
+ metadata.gz: e64bdc8330b5c64d7e7e17d44a008b0b54e60c9061f524bd1f66ededde5505a4
4
+ data.tar.gz: 0f07ebf046e8c136126bf5328d46de1dde14e4257389f490acdf4c1746f8cc0b
5
5
  SHA512:
6
- metadata.gz: c248241ffaa25a7967b2e65f8f4292d0fc64428d24f3526b8dfa5515936b241f4fdc3e48861583697a9a259002590fdfd9eace692d364d8ec2ca1d5f2475f4fa
7
- data.tar.gz: b0244677fe2fa04e995a3b7593e341b02018c9d9e197a6783f9c17332fda7821ed770f202ec854f7218ffd6439c6d3200ac0b2a101ed5f03699d3d0f3ee38597
6
+ metadata.gz: 95c29b91172bfe37d18607199b7ffd103b54b1cfbafe0ba8a5f4bbb3c70b21ec0357a49783dfeab3de9e04eea536a05513e59a5e68d307bcca186fe8671f5ce0
7
+ data.tar.gz: 9a673939c8ea707d61a8ef105e684996237198f771e8869bfa9b824e6be591e34ef461243f486af278387f7c25ff7ff74ab43c6bbcf69bba03b26d3b51ad12e1
data/bin/console CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  $: << "lib"
3
3
  require "simple/sql"
4
- require "simple/store"
4
+ #require "simple/store"
5
5
 
6
6
  SQL = Simple::SQL
7
7
  SQL.connect!
data/lib/simple/sql.rb CHANGED
@@ -8,7 +8,6 @@ require_relative "sql/helpers"
8
8
  require_relative "sql/result"
9
9
  require_relative "sql/config"
10
10
  require_relative "sql/logging"
11
- require_relative "sql/simple_transactions"
12
11
  require_relative "sql/scope"
13
12
  require_relative "sql/connection_adapter"
14
13
  require_relative "sql/connection"
@@ -20,78 +19,47 @@ module Simple
20
19
  # The Simple::SQL module
21
20
  module SQL
22
21
  extend self
22
+
23
23
  extend Forwardable
24
- delegate [:ask, :all, :each, :exec, :locked, :print] => :connection
25
- delegate [:transaction, :wait_for_notify] => :connection
24
+ delegate [:ask, :all, :each, :exec, :locked, :print, :transaction, :wait_for_notify] => :default_connection
26
25
 
27
26
  delegate [:logger, :logger=] => ::Simple::SQL::Logging
28
27
 
29
- private
30
-
31
- def connection
32
- connector.call
33
- end
34
-
35
- # The connector attribute returns a lambda, which, when called, returns a connection
36
- # object.
37
- #
38
- # If this seems weird: this is for interacting with ActiveRecord. To be in sync how
39
- # Rails handles ActiveRecord connections (it checks it out of a connection pool when
40
- # needed for the first time in a request cycle, and checks it in afterwards) we need
41
- # to make sure not to keep a reference to the actual connection object around. Instead
42
- # we need to be able to call a function (in that case ActiveRecord::Base.connection).
43
- #
44
- # In non-Rails mode the connector really is a lambda which just returns an object.
28
+ # connects to the database specified via the url parameter. If called
29
+ # without argument it tries to determine a DATABASE_URL from either the
30
+ # environment setting (DATABASE_URL) or from a config/database.yml file,
31
+ # taking into account the RAILS_ENV and RACK_ENV settings.
45
32
  #
46
- # In any case the connector is stored in a thread-safe fashion. This is not necessary
47
- # in Rails mode (because AR::B.connection itself is thread-safe already), but in non-
48
- # Rails-mode we make sure to manage one connection per thread.
49
- def connector=(connector)
50
- Thread.current[:"Simple::SQL.connector"] = connector
33
+ # Returns the connection object.
34
+ def connect(database_url = :auto)
35
+ Connection.create(database_url)
51
36
  end
52
37
 
53
- def connector
54
- Thread.current[:"Simple::SQL.connector"] ||= lambda { connect_to_active_record }
55
- end
38
+ # -- default connection ---------------------------------------------------
56
39
 
57
- def connect_to_active_record
58
- return Connection.active_record_connection if defined?(ActiveRecord)
40
+ DEFAULT_CONNECTION_KEY = :"Simple::SQL.default_connection"
59
41
 
60
- STDERR.puts <<~SQL
61
- Simple::SQL works out of the box with ActiveRecord-based postgres connections, reusing the current connection.
62
- To use without ActiveRecord you must connect to a database via Simple::SQL.connect!.
63
- SQL
64
-
65
- raise ArgumentError, "simple-sql: missing connection"
42
+ # returns the default connection.
43
+ def default_connection
44
+ Thread.current[DEFAULT_CONNECTION_KEY] ||= connect(:auto)
66
45
  end
67
46
 
68
- public
69
-
70
- # returns a configuration hash, either from the passed in database URL,
71
- # from a DATABASE_URL environment value, or from the config/database.yml
72
- # file.
73
- def configuration(database_url = :auto)
74
- database_url = Config.determine_url if database_url == :auto
75
- Config.parse_url(database_url)
76
- end
77
-
78
- # connects to the database specified via the url parameter. If called
79
- # without argument it tries to determine a DATABASE_URL from either the
80
- # environment setting (DATABASE_URL) or from a config/database.yml file,
81
- # taking into account the RAILS_ENV and RACK_ENV settings.
47
+ # connects to the database specified via the url parameter, and sets
48
+ # Simple::SQL's default connection.
49
+ #
50
+ # \see connect, default_connection
82
51
  def connect!(database_url = :auto)
83
- database_url = Config.determine_url if database_url == :auto
84
-
85
- connection = Connection.pg_connection(database_url)
86
- self.connector = lambda { connection }
52
+ disconnect!
53
+ Thread.current[DEFAULT_CONNECTION_KEY] ||= connect(database_url)
87
54
  end
88
55
 
89
- # disconnects the current connection.
56
+ # disconnects the current default connection.
90
57
  def disconnect!
91
- return unless connector
58
+ connection = Thread.current[DEFAULT_CONNECTION_KEY]
59
+ return unless connection
92
60
 
93
61
  connection.disconnect!
94
- self.connector = nil
62
+ Thread.current[DEFAULT_CONNECTION_KEY] = nil
95
63
  end
96
64
  end
97
65
  end
@@ -39,11 +39,11 @@ module Simple::SQL::Config
39
39
  username, password, host, port, database = abc.values_at "username", "password", "host", "port", "database"
40
40
 
41
41
  URI::Generic.build(
42
- scheme: "postgres",
43
- userinfo: [ username, (":" if password), password ].join,
42
+ scheme: "postgres",
43
+ userinfo: [username, (":" if password), password].join,
44
44
  host: host || "localhost",
45
45
  port: port,
46
- path: "/#{database}"
46
+ path: "/#{database}"
47
47
  ).to_s
48
48
  end
49
49
 
@@ -1,69 +1,31 @@
1
- require "pg"
2
-
3
- # private
4
- module Simple::SQL::Connection
5
- Logging = ::Simple::SQL::Logging
6
-
7
- def self.active_record_connection
8
- ActiveRecordConnection
9
- end
10
-
11
- def self.pg_connection(database_url)
12
- config = ::Simple::SQL::Config.parse_url(database_url)
13
-
14
- Logging.info "Connecting to #{database_url}"
15
-
16
- raw_connection = PG::Connection.new(config)
17
- raw_connection.set_notice_processor { |message| Logging.info(message) }
18
- PgConnection.new(raw_connection)
19
- end
20
-
21
- # A PgConnection object is built around a raw connection. It includes
22
- # the ConnectionAdapter, which implements ask, all, + friends, and also
23
- # includes a quiet simplistic Transaction implementation
24
- class PgConnection
25
- attr_reader :raw_connection
26
-
27
- def initialize(raw_connection)
28
- @raw_connection = raw_connection
29
- end
30
-
31
- def disconnect!
32
- return unless @raw_connection
33
-
34
- Logging.info "Disconnecting from database"
35
- @raw_connection.close
36
- @raw_connection = nil
1
+ # A Connection object.
2
+ #
3
+ # A Connection object is built around a raw connection (as created from the pg
4
+ # ruby gem).
5
+ #
6
+ #
7
+ # It includes
8
+ # the ConnectionAdapter, which implements ask, all, + friends, and also
9
+ # includes a quiet simplistic Transaction implementation
10
+ class Simple::SQL::Connection
11
+ def self.create(database_url = :auto)
12
+ case database_url
13
+ when :auto
14
+ if defined?(::ActiveRecord)
15
+ ActiveRecordConnection.new
16
+ else
17
+ RawConnection.new Simple::SQL::Config.determine_url
18
+ end
19
+ else
20
+ RawConnection.new database_url
37
21
  end
38
-
39
- include ::Simple::SQL::ConnectionAdapter # all, ask, first, etc.
40
- include ::Simple::SQL::SimpleTransactions # transactions
41
-
42
- extend Forwardable
43
- delegate [:wait_for_notify] => :raw_connection # wait_for_notify
44
22
  end
45
23
 
46
- module ActiveRecordConnection
47
- extend self
48
-
49
- extend ::Simple::SQL::ConnectionAdapter # all, ask, first, etc.
50
-
51
- extend Forwardable
52
- delegate [:transaction] => :connection # transactions
53
- delegate [:wait_for_notify] => :raw_connection # wait_for_notify
54
-
55
- def raw_connection
56
- ActiveRecord::Base.connection.raw_connection
57
- end
24
+ include Simple::SQL::ConnectionAdapter
58
25
 
59
- def disconnect!
60
- # This doesn't really disconnect. We hope ActiveRecord puts the connection
61
- # back into the connection pool instead.
62
- @raw_connection = nil
63
- end
64
-
65
- def connection
66
- ActiveRecord::Base.connection
67
- end
68
- end
26
+ extend Forwardable
27
+ delegate [:wait_for_notify] => :raw_connection
69
28
  end
29
+
30
+ require_relative "connection/raw_connection"
31
+ require_relative "connection/active_record_connection"
@@ -0,0 +1,13 @@
1
+ class Simple::SQL::Connection::ActiveRecordConnection < Simple::SQL::Connection
2
+ def initialize
3
+ ::ActiveRecord::Base.connection
4
+ end
5
+
6
+ def raw_connection
7
+ ::ActiveRecord::Base.connection.raw_connection
8
+ end
9
+
10
+ def transaction(&block)
11
+ ::ActiveRecord::Base.connection.transaction(&block)
12
+ end
13
+ end
@@ -1,14 +1,32 @@
1
- # private
2
- module Simple::SQL::SimpleTransactions
3
- def tx_nesting_level
4
- @tx_nesting_level ||= 0
1
+ require "pg"
2
+
3
+ class Simple::SQL::Connection::RawConnection < Simple::SQL::Connection
4
+ SELF = self
5
+
6
+ attr_reader :raw_connection
7
+
8
+ def initialize(database_url)
9
+ @database_url = database_url
10
+ @raw_connection = PG::Connection.new(@database_url)
11
+ ObjectSpace.define_finalizer(self, SELF.finalizer(@raw_connection))
12
+ end
13
+
14
+ def self.finalizer(raw_connection)
15
+ proc do
16
+ raw_connection.finish unless raw_connection.finished?
17
+ end
5
18
  end
6
19
 
7
- def tx_nesting_level=(tx_nesting_level)
8
- @tx_nesting_level = tx_nesting_level
20
+ def disconnect!
21
+ return unless @raw_connection
22
+
23
+ @raw_connection.finish unless @raw_connection.finished?
24
+ @raw_connection = nil
9
25
  end
10
26
 
11
27
  def transaction(&_block)
28
+ @tx_nesting_level ||= 0
29
+
12
30
  # Notes: by using "ensure" (as opposed to rescue) we are rolling back
13
31
  # both when an exception was raised and when a value was thrown. This
14
32
  # also means we have to track whether or not to rollback. i.e. do roll
@@ -18,12 +36,12 @@ module Simple::SQL::SimpleTransactions
18
36
  # Rolling back from inside a nested transaction would require SAVEPOINT
19
37
  # support; without the code is simpler at least :)
20
38
 
21
- if tx_nesting_level == 0
39
+ if @tx_nesting_level == 0
22
40
  exec "BEGIN"
23
41
  tx_started = true
24
42
  end
25
43
 
26
- self.tx_nesting_level += 1
44
+ @tx_nesting_level += 1
27
45
 
28
46
  return_value = yield
29
47
 
@@ -35,7 +53,7 @@ module Simple::SQL::SimpleTransactions
35
53
 
36
54
  return_value
37
55
  ensure
38
- self.tx_nesting_level -= 1
56
+ @tx_nesting_level -= 1
39
57
  if tx_started && !tx_committed
40
58
  exec "ROLLBACK"
41
59
  end
@@ -16,47 +16,11 @@ module Simple
16
16
  sql
17
17
  end
18
18
 
19
- def pretty_format(sql, *args)
20
- sql = if use_pg_format?
21
- pg_format_sql(sql)
22
- else
23
- format_sql(sql)
24
- end
25
-
26
- args = args.map(&:inspect).join(", ")
27
- "#{sql} w/args: #{args}"
28
- end
29
-
30
19
  private
31
20
 
32
21
  def format_sql(sql)
33
22
  sql.gsub(/\s*\n\s*/, " ").gsub(/(\A\s+)|(\s+\z)/, "")
34
23
  end
35
-
36
- require "open3"
37
-
38
- def use_pg_format?
39
- return @use_pg_format unless @use_pg_format.nil?
40
-
41
- `which pg_format`
42
- if $?.exitstatus == 0
43
- @use_pg_format = true
44
- else
45
- Simple::SQL.logger.warn "[sql] simple-sql can use pg_format for logging queries. Please see https://github.com/darold/pgFormatter"
46
- @use_pg_format = false
47
- end
48
- end
49
-
50
- PG_FORMAT_ARGS = "--function-case 2 --maxlength 15000 --nocomment --spaces 2 --keyword-case 2 --no-comma-end"
51
-
52
- def pg_format_sql(sql)
53
- stdin, stdout, _ = Open3.popen2("pg_format #{PG_FORMAT_ARGS} -")
54
- stdin.print sql
55
- stdin.close
56
- formatted = stdout.read
57
- stdout.close
58
- formatted
59
- end
60
24
  end
61
25
  end
62
26
  end
@@ -87,8 +87,8 @@ module Simple
87
87
  return if sql =~ /^EXPLAIN /
88
88
 
89
89
  log_multiple_lines ::Logger::WARN, prefix: "[sql-slow]" do
90
- formatted_query = Formatting.pretty_format(sql, *args)
91
- query_plan = ::Simple::SQL.all "EXPLAIN ANALYZE #{sql}", *args
90
+ formatted_query = Formatting.format(sql, *args)
91
+ query_plan = ::Simple::SQL.all "EXPLAIN #{sql}", *args
92
92
 
93
93
  <<~MSG
94
94
  === slow query detected: (#{'%.3f secs' % runtime}) ===================================================================================
@@ -1,4 +1,3 @@
1
- # rubocop:disable Metrics/AbcSize
2
1
  # rubocop:disable Naming/AccessorMethodName
3
2
 
4
3
  require_relative "helpers"
@@ -31,21 +30,54 @@ class ::Simple::SQL::Result < Array
31
30
  replace(records)
32
31
  end
33
32
 
33
+ # returns a fast estimate for the total_count of search hits
34
+ #
35
+ # This is filled in when resolving a paginated scope.
36
+ def total_count_estimate
37
+ @total_count_estimate ||= catch(:total_count_estimate) do
38
+ scope = @pagination_scope
39
+ scope_sql = scope.order_by(nil).to_sql(pagination: false)
40
+ ::Simple::SQL.each("EXPLAIN #{scope_sql}", *scope.args) do |line|
41
+ next unless line =~ /\brows=(\d+)/
42
+
43
+ throw :total_count_estimate, Integer($1)
44
+ end
45
+ -1
46
+ end
47
+ end
48
+
49
+ # returns the estimated total number of pages of search hits
50
+ #
51
+ # This is filled in when resolving a paginated scope.
52
+ def total_pages_estimate
53
+ @total_pages_estimate ||= (total_count_estimate * 1.0 / @pagination_scope.per).ceil
54
+ end
55
+
34
56
  # returns the total_count of search hits
35
57
  #
36
58
  # This is filled in when resolving a paginated scope.
37
- attr_reader :total_count
59
+ def total_count
60
+ @total_count ||= begin
61
+ scope = @pagination_scope
62
+ scope_sql = scope.order_by(nil).to_sql(pagination: false)
63
+ ::Simple::SQL.ask("SELECT COUNT(*) FROM (#{scope_sql}) simple_sql_count", *scope.args)
64
+ end
65
+ end
38
66
 
39
67
  # returns the total number of pages of search hits
40
68
  #
41
69
  # This is filled in when resolving a paginated scope. It takes
42
70
  # into account the scope's "per" option.
43
- attr_reader :total_pages
71
+ def total_pages
72
+ @total_pages ||= (total_count * 1.0 / @pagination_scope.per).ceil
73
+ end
44
74
 
45
75
  # returns the current page number in a paginated search
46
76
  #
47
77
  # This is filled in when resolving a paginated scope.
48
- attr_reader :current_page
78
+ def current_page
79
+ @current_page ||= @pagination_scope.page
80
+ end
49
81
 
50
82
  private
51
83
 
@@ -56,14 +88,13 @@ class ::Simple::SQL::Result < Array
56
88
  # This branch is an optimization: the call to the database to count is
57
89
  # not necessary if we know that there are not even any results on the
58
90
  # first page.
59
- @total_count = 0
60
91
  @current_page = 1
92
+ @total_count = 0
93
+ @total_pages = 1
94
+ @total_count_estimate = 0
95
+ @total_pages_estimate = 1
61
96
  else
62
- sql = "SELECT COUNT(*) FROM (#{scope.order_by(nil).to_sql(pagination: false)}) simple_sql_count"
63
- @total_count = ::Simple::SQL.ask(sql, *scope.args)
64
- @current_page = scope.page
97
+ @pagination_scope = scope
65
98
  end
66
-
67
- @total_pages = (@total_count * 1.0 / scope.per).ceil
68
99
  end
69
100
  end
@@ -24,11 +24,11 @@ class Simple::SQL::Scope
24
24
  #
25
25
  # Simple::SQL::Scope.new(table: "mytable", select: "*", where: { id: 1, foo: "bar" }, order_by: "id desc")
26
26
  #
27
- def initialize(sql)
27
+ def initialize(sql, args = [])
28
28
  expect! sql => [String, Hash]
29
29
 
30
30
  @sql = nil
31
- @args = []
31
+ @args = args
32
32
  @filters = []
33
33
 
34
34
  case sql
@@ -1,5 +1,5 @@
1
1
  module Simple
2
2
  module SQL
3
- VERSION = "0.4.32"
3
+ VERSION = "0.4.35"
4
4
  end
5
5
  end
@@ -4,10 +4,18 @@ require "simplecov"
4
4
  # care of merging these results automatically.
5
5
  SimpleCov.command_name "test:#{ENV['USE_ACTIVE_RECORD']}"
6
6
 
7
+ USE_ACTIVE_RECORD = ENV["USE_ACTIVE_RECORD"] == "1"
8
+
7
9
  SimpleCov.start do
8
10
  # return true to remove src from coverage
9
11
  add_filter do |src|
10
- src.filename =~ /\/spec\//
12
+ next true if src.filename =~ /\/spec\//
13
+ next true if src.filename =~ /\/immutable\.rb/
14
+
15
+ next true if USE_ACTIVE_RECORD && src.filename =~ /\/sql\/connection\/raw_connection\.rb/
16
+ next true if !USE_ACTIVE_RECORD && src.filename =~ /\/sql\/connection\/active_record_connection\.rb/
17
+
18
+ false
11
19
  end
12
20
 
13
21
  minimum_coverage 90
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simple-sql
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.32
4
+ version: 0.4.35
5
5
  platform: ruby
6
6
  authors:
7
7
  - radiospiel
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2019-02-22 00:00:00.000000000 Z
12
+ date: 2019-03-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: pg_array_parser
@@ -201,6 +201,8 @@ files:
201
201
  - lib/simple/sql.rb
202
202
  - lib/simple/sql/config.rb
203
203
  - lib/simple/sql/connection.rb
204
+ - lib/simple/sql/connection/active_record_connection.rb
205
+ - lib/simple/sql/connection/raw_connection.rb
204
206
  - lib/simple/sql/connection_adapter.rb
205
207
  - lib/simple/sql/duplicate.rb
206
208
  - lib/simple/sql/formatting.rb
@@ -219,7 +221,6 @@ files:
219
221
  - lib/simple/sql/scope/filters.rb
220
222
  - lib/simple/sql/scope/order.rb
221
223
  - lib/simple/sql/scope/pagination.rb
222
- - lib/simple/sql/simple_transactions.rb
223
224
  - lib/simple/sql/version.rb
224
225
  - log/.gitkeep
225
226
  - scripts/stats
@@ -266,7 +267,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
266
267
  - !ruby/object:Gem::Version
267
268
  version: '0'
268
269
  requirements: []
269
- rubygems_version: 3.0.2
270
+ rubyforge_project:
271
+ rubygems_version: 2.7.6
270
272
  signing_key:
271
273
  specification_version: 4
272
274
  summary: SQL with a simple interface