simple-sql 0.4.32 → 0.4.35

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: 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