sequel 5.9.0 → 5.10.0
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 +4 -4
- data/CHANGELOG +34 -0
- data/doc/release_notes/5.10.0.txt +84 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +1 -1
- data/lib/sequel/adapters/postgres.rb +20 -4
- data/lib/sequel/adapters/shared/postgres.rb +12 -2
- data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -0
- data/lib/sequel/database/schema_generator.rb +3 -0
- data/lib/sequel/database/schema_methods.rb +2 -0
- data/lib/sequel/dataset/actions.rb +7 -6
- data/lib/sequel/dataset/misc.rb +14 -0
- data/lib/sequel/extensions/pg_array.rb +83 -79
- data/lib/sequel/extensions/pg_extended_date_support.rb +11 -4
- data/lib/sequel/extensions/pg_range.rb +4 -2
- data/lib/sequel/model/associations.rb +10 -2
- data/lib/sequel/plugins/list.rb +18 -8
- data/lib/sequel/plugins/pg_array_associations.rb +2 -2
- data/lib/sequel/plugins/tree.rb +28 -13
- data/lib/sequel/sql.rb +24 -4
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +94 -26
- data/spec/bin_spec.rb +2 -0
- data/spec/core/dataset_spec.rb +58 -32
- data/spec/core/expression_filters_spec.rb +16 -0
- data/spec/core/spec_helper.rb +1 -0
- data/spec/core_extensions_spec.rb +1 -0
- data/spec/extensions/list_spec.rb +16 -0
- data/spec/extensions/pg_array_associations_spec.rb +10 -10
- data/spec/extensions/pg_range_spec.rb +34 -2
- data/spec/extensions/spec_helper.rb +1 -0
- data/spec/extensions/tree_spec.rb +40 -0
- data/spec/guards_helper.rb +1 -0
- data/spec/model/associations_spec.rb +21 -0
- data/spec/model/spec_helper.rb +1 -0
- metadata +4 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 629d77185f762e5399741ae9b1caf03c8cd2bbe5f72fd9487900dc0c4bfc2e7a
         | 
| 4 | 
            +
              data.tar.gz: ee46b9c3180a67eb356b1f2ae7b274cc293687b333bf216b76a3e55def081c57
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 2b5a5c9ed14dcd9bc6c98286bb4c22637052bb2f152f352d5002b096e2d361e2ac6ec15dcd44881e3e96e37b6acd8896161ad94cac4b674d55a6f54a9d43a948
         | 
| 7 | 
            +
              data.tar.gz: 847891aeebf175eca649d58461c38d3127311594bb3906bb73429726230b57dd1555ffbd760e3d5f7b30bb9907d5c6d461af1ac0d3b8e166b8dcbf61787fea97
         | 
    
        data/CHANGELOG
    CHANGED
    
    | @@ -1,3 +1,37 @@ | |
| 1 | 
            +
            === 5.10.0 (2018-07-01)
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            * Use input type casts when using the postgres adapter with pg 0.18+ to reduce string allocations for some primitive types used as prepared statement arguments (jeremyevans)
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            * Assume local time if database timezone not specified when handling BC timestamps on JRuby 9.2.0.0 in the pg_extended_date_support extension (jeremyevans)
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            * Fix parsing of timetz types in the jdbc/postgresql adapter (jeremyevans)
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            * Make SQLTime.parse respect SQLTime.date and Sequel.application_timezone (jeremyevans)
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            * Add :top as an option in the list plugin (celsworth) (#1526)
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            * Fix Model#{ancestors,descendants,self_and_siblings} in the tree plugin when custom parent/children association names are used (jeremyevans) (#1525)
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            * Treat read-only mode error as disconnect error on mysql and mysql2 adapters, for better behavior on AWS Aurora cluster (jeremyevans)
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            * Don't use cached placeholder literalizers for in Dataset#{first,where_all,where_each,where_single_value} if argument is empty array or hash (jeremyevans)
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            * Support :tablespace option when adding tables, indexes, and materialized views on PostgreSQL (jeremyevans)
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            * Support :include option for indexes on PostgreSQL 11+ (jeremyevans)
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            * Allow the use of IN/NOT IN operators with set returning functions for Sequel::Model datasets (jeremyevans)
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            * Make many_to_pg_array associations in the pg_array_associations plugin work on PostgreSQL 11 (jeremyevans)
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            * Only load strscan library in pg_array extension if it is needed (jeremyevans)
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            * Don't remove related many_to_one associations from cache when setting column value to existing value for model instances that have not been persisted (jeremyevans) (#1521)
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            * Support ruby 2.6+ endless ranges in the pg_range extension (jeremyevans)
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            * Support ruby 2.6+ endless ranges in filters, using just a >= operator for them (jeremyevans)
         | 
| 34 | 
            +
             | 
| 1 35 | 
             
            === 5.9.0 (2018-06-01)
         | 
| 2 36 |  | 
| 3 37 | 
             
            * Support generated columns on MySQL 5.7+ and MariaDB 5.2+ (wjordan, jeremyevans) (#1517)
         | 
| @@ -0,0 +1,84 @@ | |
| 1 | 
            +
            = New Features
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            * Ruby 2.6+ endless ranges are now supported as condition specifier
         | 
| 4 | 
            +
              values, using a >= operator for them:
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                DB[:t].where(c: 1...)
         | 
| 7 | 
            +
                # SELECT * FROM t WHERE (c >= 1)
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            * Ruby 2.6+ endless ranges are now supported in the pg_range
         | 
| 10 | 
            +
              extension:
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                DB[:t].where(id: 1).update(r: 1...)
         | 
| 13 | 
            +
                # UPDATE t SET r = '[1,)' WHERE (id = 1)
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            * The :include option when creating indexes is now supported on
         | 
| 16 | 
            +
              PostgreSQL 11, specifying additional columns to include in the index
         | 
| 17 | 
            +
              without indexing them.  This is useful to allow index only scans in
         | 
| 18 | 
            +
              additional cases.
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            * The :tablespace option is now supported when creating tables,
         | 
| 21 | 
            +
              indexes, and materialized views on PostgreSQL.
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            * The list plugin now supports a :top option, which can be used to
         | 
| 24 | 
            +
              specify the top of the list.  The default value for the top of the
         | 
| 25 | 
            +
              list is 1, but using this option you can make the top of the list be
         | 
| 26 | 
            +
              0.
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            = Other Improvements
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            * In the pg_array_associations plugin, filtering by associations for
         | 
| 31 | 
            +
              many_to_pg_array associations now works correctly on PostgreSQL 11.
         | 
| 32 | 
            +
              Previously it did not work on PostgreSQL 11 due to new restrictions
         | 
| 33 | 
            +
              on using set returning functions in the the SELECT list.
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            * When setting the value of a column to the same value the column
         | 
| 36 | 
            +
              already has, for a new model object that has not yet been persisted,
         | 
| 37 | 
            +
              where the column is used as the foreign key for at least one
         | 
| 38 | 
            +
              many_to_one association, do not clear any related associations from
         | 
| 39 | 
            +
              the associations cache.
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            * In the pg_array extension, if there are separate conversion procs for
         | 
| 42 | 
            +
              timetz and time types, the conversion proc for the timetz[] type now
         | 
| 43 | 
            +
              correctly uses the conversion proc for the timetz type to convert
         | 
| 44 | 
            +
              scalar values, instead of the conversion proc for the time type.
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            * Empty arrays and hashes are now correctly handled in
         | 
| 47 | 
            +
              Dataset#{first,where_all,where_each,where_single_value} when a
         | 
| 48 | 
            +
              cached placeholder literalizer is used.
         | 
| 49 | 
            +
             | 
| 50 | 
            +
            * In the tree plugin, Model#{ancestors,descendants,self_and_siblings}
         | 
| 51 | 
            +
              now work correctly when custom parent/children association names
         | 
| 52 | 
            +
              are used.
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            * The inner loop of the postgres adapter row fetching code is now
         | 
| 55 | 
            +
              2-3% faster.
         | 
| 56 | 
            +
             | 
| 57 | 
            +
            * When using the postgres adapter with pg-0.18+, set a
         | 
| 58 | 
            +
              type_map_for_queries for the connection to allow it to handle input
         | 
| 59 | 
            +
              type casts for Integer, Float, TrueClass, and FalseClass values
         | 
| 60 | 
            +
              without allocating strings.
         | 
| 61 | 
            +
             | 
| 62 | 
            +
            * SQLTime.parse (and therefore Sequel.string_to_time) now respects the
         | 
| 63 | 
            +
              SQLTime.date and Sequel.application_timezone settings.
         | 
| 64 | 
            +
             | 
| 65 | 
            +
            * The jdbc/postgresql adapter now correctly parses timetz types.
         | 
| 66 | 
            +
             | 
| 67 | 
            +
            * On JRuby 9.2.0.0, when handling BC timestamps without timezones in
         | 
| 68 | 
            +
              the pg_extended_date_support extension, assume local time and not
         | 
| 69 | 
            +
              UTC time if the database timezone is not specified and
         | 
| 70 | 
            +
              Sequel.datetime_class is Time.
         | 
| 71 | 
            +
             | 
| 72 | 
            +
            * Errors indicating that a MySQL database is in read-only mode are
         | 
| 73 | 
            +
              now treated as disconnect errors in the mysql and mysql2 adapters,
         | 
| 74 | 
            +
              for better behavior in failover scenarios.
         | 
| 75 | 
            +
             | 
| 76 | 
            +
            * Sequel::Model datasets now support the use of IN/NOT IN operators
         | 
| 77 | 
            +
              where the second argument for the operator (the right hand side) is
         | 
| 78 | 
            +
              a set returning function.  Previously, the Sequel::Model code
         | 
| 79 | 
            +
              assumed the right hand side of an IN/NOT IN operator was a datasets
         | 
| 80 | 
            +
              or array, since those are the only values where Sequel will
         | 
| 81 | 
            +
              automatically create such an operator.
         | 
| 82 | 
            +
             | 
| 83 | 
            +
            * Sequel no longer loads the strscan library in the pg_array extension
         | 
| 84 | 
            +
              if it is not necessary because the parser from sequel_pg is used.
         | 
| @@ -214,7 +214,7 @@ module Sequel | |
| 214 214 | 
             
                    STRING_TYPE = Java::JavaSQL::Types::VARCHAR
         | 
| 215 215 | 
             
                    ARRAY_TYPE = Java::JavaSQL::Types::ARRAY
         | 
| 216 216 | 
             
                    ARRAY_METHOD = Postgres.method(:RubyPGArray)
         | 
| 217 | 
            -
                    PG_SPECIFIC_TYPES = [ARRAY_TYPE, Java::JavaSQL::Types::OTHER, Java::JavaSQL::Types::STRUCT].freeze
         | 
| 217 | 
            +
                    PG_SPECIFIC_TYPES = [ARRAY_TYPE, Java::JavaSQL::Types::OTHER, Java::JavaSQL::Types::STRUCT, Java::JavaSQL::Types::TIME_WITH_TIMEZONE, Java::JavaSQL::Types::TIME].freeze
         | 
| 218 218 | 
             
                    HSTORE_METHOD = Postgres.method(:RubyPGHstore)
         | 
| 219 219 |  | 
| 220 220 | 
             
                    def type_convertor(map, meta, type, i)
         | 
| @@ -15,6 +15,12 @@ begin | |
| 15 15 | 
             
              end
         | 
| 16 16 |  | 
| 17 17 | 
             
              Sequel::Postgres::USES_PG = true
         | 
| 18 | 
            +
              if defined?(PG::TypeMapByClass)
         | 
| 19 | 
            +
                type_map = Sequel::Postgres::PG_QUERY_TYPE_MAP = PG::TypeMapByClass.new
         | 
| 20 | 
            +
                type_map[Integer] = PG::TextEncoder::Integer.new
         | 
| 21 | 
            +
                type_map[FalseClass] = type_map[TrueClass] = PG::TextEncoder::Boolean.new
         | 
| 22 | 
            +
                type_map[Float] = PG::TextEncoder::Float.new
         | 
| 23 | 
            +
              end
         | 
| 18 24 | 
             
            rescue LoadError => e 
         | 
| 19 25 | 
             
              begin
         | 
| 20 26 | 
             
                require 'postgres-pr/postgres-compat'
         | 
| @@ -211,6 +217,9 @@ module Sequel | |
| 211 217 | 
             
                    end
         | 
| 212 218 |  | 
| 213 219 | 
             
                    conn.instance_variable_set(:@db, self)
         | 
| 220 | 
            +
                    if USES_PG && conn.respond_to?(:type_map_for_queries=) && defined?(PG_QUERY_TYPE_MAP)
         | 
| 221 | 
            +
                      conn.type_map_for_queries = PG_QUERY_TYPE_MAP
         | 
| 222 | 
            +
                    end
         | 
| 214 223 |  | 
| 215 224 | 
             
                    if encoding = opts[:encoding] || opts[:charset]
         | 
| 216 225 | 
             
                      if conn.respond_to?(:set_client_encoding)
         | 
| @@ -737,9 +746,9 @@ module Sequel | |
| 737 746 | 
             
                    cols = []
         | 
| 738 747 | 
             
                    procs = db.conversion_procs
         | 
| 739 748 | 
             
                    res.nfields.times do |fieldnum|
         | 
| 740 | 
            -
                      cols << [ | 
| 749 | 
            +
                      cols << [procs[res.ftype(fieldnum)], output_identifier(res.fname(fieldnum))]
         | 
| 741 750 | 
             
                    end
         | 
| 742 | 
            -
                    self.columns = cols.map{|c| c[ | 
| 751 | 
            +
                    self.columns = cols.map{|c| c[1]}
         | 
| 743 752 | 
             
                    cols
         | 
| 744 753 | 
             
                  end
         | 
| 745 754 |  | 
| @@ -756,13 +765,20 @@ module Sequel | |
| 756 765 | 
             
                  # For each row in the result set, yield a hash with column name symbol
         | 
| 757 766 | 
             
                  # keys and typecasted values.
         | 
| 758 767 | 
             
                  def yield_hash_rows(res, cols)
         | 
| 759 | 
            -
                    res.ntuples | 
| 768 | 
            +
                    ntuples = res.ntuples
         | 
| 769 | 
            +
                    recnum = 0
         | 
| 770 | 
            +
                    while recnum < ntuples
         | 
| 771 | 
            +
                      fieldnum = 0
         | 
| 772 | 
            +
                      nfields = cols.length
         | 
| 760 773 | 
             
                      converted_rec = {}
         | 
| 761 | 
            -
                       | 
| 774 | 
            +
                      while fieldnum < nfields
         | 
| 775 | 
            +
                        type_proc, fieldsym = cols[fieldnum]
         | 
| 762 776 | 
             
                        value = res.getvalue(recnum, fieldnum)
         | 
| 763 777 | 
             
                        converted_rec[fieldsym] = (value && type_proc) ? type_proc.call(value) : value
         | 
| 778 | 
            +
                        fieldnum += 1 
         | 
| 764 779 | 
             
                      end
         | 
| 765 780 | 
             
                      yield converted_rec
         | 
| 781 | 
            +
                      recnum += 1
         | 
| 766 782 | 
             
                    end
         | 
| 767 783 | 
             
                  end
         | 
| 768 784 | 
             
                end
         | 
| @@ -1042,6 +1042,10 @@ module Sequel | |
| 1042 1042 | 
             
                      sql += " ON COMMIT #{ON_COMMIT[on_commit]}"
         | 
| 1043 1043 | 
             
                    end
         | 
| 1044 1044 |  | 
| 1045 | 
            +
                    if tablespace = options[:tablespace]
         | 
| 1046 | 
            +
                      sql += " TABLESPACE #{quote_identifier(tablespace)}"
         | 
| 1047 | 
            +
                    end
         | 
| 1048 | 
            +
             | 
| 1045 1049 | 
             
                    if server = options[:foreign]
         | 
| 1046 1050 | 
             
                      sql += " SERVER #{quote_identifier(server)}"
         | 
| 1047 1051 | 
             
                      if foreign_opts = options[:options]
         | 
| @@ -1077,7 +1081,13 @@ module Sequel | |
| 1077 1081 |  | 
| 1078 1082 | 
             
                  # DDL fragment for initial part of CREATE VIEW statement
         | 
| 1079 1083 | 
             
                  def create_view_prefix_sql(name, options)
         | 
| 1080 | 
            -
                    create_view_sql_append_columns("CREATE #{'OR REPLACE 'if options[:replace]}#{'TEMPORARY 'if options[:temp]}#{'RECURSIVE ' if options[:recursive]}#{'MATERIALIZED ' if options[:materialized]}VIEW #{quote_schema_table(name)}", options[:columns] || options[:recursive])
         | 
| 1084 | 
            +
                    sql = create_view_sql_append_columns("CREATE #{'OR REPLACE 'if options[:replace]}#{'TEMPORARY 'if options[:temp]}#{'RECURSIVE ' if options[:recursive]}#{'MATERIALIZED ' if options[:materialized]}VIEW #{quote_schema_table(name)}", options[:columns] || options[:recursive])
         | 
| 1085 | 
            +
             | 
| 1086 | 
            +
                    if tablespace = options[:tablespace]
         | 
| 1087 | 
            +
                      sql += " TABLESPACE #{quote_identifier(tablespace)}"
         | 
| 1088 | 
            +
                    end
         | 
| 1089 | 
            +
             | 
| 1090 | 
            +
                    sql
         | 
| 1081 1091 | 
             
                  end
         | 
| 1082 1092 |  | 
| 1083 1093 | 
             
                  # SQL for dropping a function from the database.
         | 
| @@ -1147,7 +1157,7 @@ module Sequel | |
| 1147 1157 | 
             
                    when :spatial
         | 
| 1148 1158 | 
             
                      index_type = :gist
         | 
| 1149 1159 | 
             
                    end
         | 
| 1150 | 
            -
                    "CREATE #{unique}INDEX#{' CONCURRENTLY' if index[:concurrently]}#{if_not_exists} #{quote_identifier(index_name)} ON #{quote_schema_table(table_name)} #{"USING #{index_type} " if index_type}#{expr}#{filter}"
         | 
| 1160 | 
            +
                    "CREATE #{unique}INDEX#{' CONCURRENTLY' if index[:concurrently]}#{if_not_exists} #{quote_identifier(index_name)} ON #{quote_schema_table(table_name)} #{"USING #{index_type} " if index_type}#{expr}#{" INCLUDE #{literal(Array(index[:include]))}" if index[:include]}#{" TABLESPACE #{quote_identifier(index[:tablespace])}" if index[:tablespace]}#{filter}"
         | 
| 1151 1161 | 
             
                  end
         | 
| 1152 1162 |  | 
| 1153 1163 | 
             
                  # Setup datastructures shared by all postgres adapters.
         | 
| @@ -17,6 +17,7 @@ module Sequel | |
| 17 17 | 
             
                    MySQL client is not connected
         | 
| 18 18 | 
             
                    This connection is still waiting for a result, try again once you have the result
         | 
| 19 19 | 
             
                    closed MySQL connection
         | 
| 20 | 
            +
                    The MySQL server is running with the --read-only option so it cannot execute this statement
         | 
| 20 21 | 
             
                    END
         | 
| 21 22 | 
             
                    # Error messages for mysql and mysql2 that indicate the current connection should be disconnected
         | 
| 22 23 | 
             
                    MYSQL_DATABASE_DISCONNECT_ERRORS = /\A#{Regexp.union(disconnect_errors)}/
         | 
| @@ -222,6 +222,9 @@ module Sequel | |
| 222 222 | 
             
                  #                  operations on the table while the index is being
         | 
| 223 223 | 
             
                  #                  built.
         | 
| 224 224 | 
             
                  # :opclass :: Use a specific operator class in the index.
         | 
| 225 | 
            +
                  # :include :: Include additional column values in the index, without
         | 
| 226 | 
            +
                  #             actually indexing on those values (PostgreSQL 11+).
         | 
| 227 | 
            +
                  # :tablespace :: Specify tablespace for index.
         | 
| 225 228 | 
             
                  #
         | 
| 226 229 | 
             
                  # Microsoft SQL Server specific options:
         | 
| 227 230 | 
             
                  #
         | 
| @@ -181,6 +181,7 @@ module Sequel | |
| 181 181 | 
             
                #             where keys are option names and values are option values.  Note
         | 
| 182 182 | 
             
                #             that option names are unquoted, so you should not use untrusted
         | 
| 183 183 | 
             
                #             keys.
         | 
| 184 | 
            +
                # :tablespace :: The tablespace to use for the table.
         | 
| 184 185 | 
             
                #
         | 
| 185 186 | 
             
                # See <tt>Schema::CreateTableGenerator</tt> and the {"Schema Modification" guide}[rdoc-ref:doc/schema_modification.rdoc].
         | 
| 186 187 | 
             
                def create_table(name, options=OPTS, &block)
         | 
| @@ -281,6 +282,7 @@ module Sequel | |
| 281 282 | 
             
                #               in a subquery, if you are providing a Dataset as the source
         | 
| 282 283 | 
             
                #               argument, if should probably call the union method with the
         | 
| 283 284 | 
             
                #               all: true and from_self: false options.
         | 
| 285 | 
            +
                # :tablespace :: The tablespace to use for materialized views.
         | 
| 284 286 | 
             
                def create_view(name, source, options = OPTS)
         | 
| 285 287 | 
             
                  execute_ddl(create_view_sql(name, source, options))
         | 
| 286 288 | 
             
                  remove_cached_schema(name)
         | 
| @@ -231,10 +231,11 @@ module Sequel | |
| 231 231 |  | 
| 232 232 | 
             
                      return res
         | 
| 233 233 | 
             
                    end
         | 
| 234 | 
            +
                    where_args = args
         | 
| 234 235 | 
             
                    args = arg
         | 
| 235 236 | 
             
                  end
         | 
| 236 237 |  | 
| 237 | 
            -
                  if loader =  | 
| 238 | 
            +
                  if loader = cached_where_placeholder_literalizer(where_args||args, block, :_first_cond_loader) do |pl|
         | 
| 238 239 | 
             
                      _single_record_ds.where(pl.arg)
         | 
| 239 240 | 
             
                    end
         | 
| 240 241 |  | 
| @@ -875,7 +876,7 @@ module Sequel | |
| 875 876 | 
             
                #   DB[:table].where_all(id: [1,2,3])
         | 
| 876 877 | 
             
                #   # SELECT * FROM table WHERE (id IN (1, 2, 3))
         | 
| 877 878 | 
             
                def where_all(cond, &block)
         | 
| 878 | 
            -
                  if loader = _where_loader
         | 
| 879 | 
            +
                  if loader = _where_loader([cond], nil)
         | 
| 879 880 | 
             
                    loader.all(filter_expr(cond), &block)
         | 
| 880 881 | 
             
                  else
         | 
| 881 882 | 
             
                    where(cond).all(&block)
         | 
| @@ -889,7 +890,7 @@ module Sequel | |
| 889 890 | 
             
                #   DB[:table].where_each(id: [1,2,3]){|row| p row}
         | 
| 890 891 | 
             
                #   # SELECT * FROM table WHERE (id IN (1, 2, 3))
         | 
| 891 892 | 
             
                def where_each(cond, &block)
         | 
| 892 | 
            -
                  if loader = _where_loader
         | 
| 893 | 
            +
                  if loader = _where_loader([cond], nil)
         | 
| 893 894 | 
             
                    loader.each(filter_expr(cond), &block)
         | 
| 894 895 | 
             
                  else
         | 
| 895 896 | 
             
                    where(cond).each(&block)
         | 
| @@ -904,7 +905,7 @@ module Sequel | |
| 904 905 | 
             
                #   DB[:table].select(:name).where_single_value(id: 1)
         | 
| 905 906 | 
             
                #   # SELECT name FROM table WHERE (id = 1) LIMIT 1
         | 
| 906 907 | 
             
                def where_single_value(cond)
         | 
| 907 | 
            -
                  if loader =  | 
| 908 | 
            +
                  if loader = cached_where_placeholder_literalizer([cond], nil, :_where_single_value_loader) do |pl|
         | 
| 908 909 | 
             
                      single_value_ds.where(pl.arg)
         | 
| 909 910 | 
             
                    end
         | 
| 910 911 |  | 
| @@ -1039,8 +1040,8 @@ module Sequel | |
| 1039 1040 | 
             
                end
         | 
| 1040 1041 |  | 
| 1041 1042 | 
             
                # Loader used for where_all and where_each.
         | 
| 1042 | 
            -
                def _where_loader
         | 
| 1043 | 
            -
                   | 
| 1043 | 
            +
                def _where_loader(where_args, where_block)
         | 
| 1044 | 
            +
                  cached_where_placeholder_literalizer(where_args, where_block, :_where_loader) do |pl|
         | 
| 1044 1045 | 
             
                    where(pl.arg)
         | 
| 1045 1046 | 
             
                  end
         | 
| 1046 1047 | 
             
                end
         | 
    
        data/lib/sequel/dataset/misc.rb
    CHANGED
    
    | @@ -309,6 +309,20 @@ module Sequel | |
| 309 309 | 
             
                  loader
         | 
| 310 310 | 
             
                end
         | 
| 311 311 |  | 
| 312 | 
            +
                # Return a cached placeholder literalizer for the key, unless where_block is
         | 
| 313 | 
            +
                # nil and where_args is an empty array or hash.  This is designed to guard
         | 
| 314 | 
            +
                # against placeholder literalizer use when passing arguments to where
         | 
| 315 | 
            +
                # in the uncached case and filter_expr if a cached placeholder literalizer
         | 
| 316 | 
            +
                # is used.
         | 
| 317 | 
            +
                def cached_where_placeholder_literalizer(where_args, where_block, key, &block)
         | 
| 318 | 
            +
                  where_args = where_args[0] if where_args.length == 1
         | 
| 319 | 
            +
                  unless where_block
         | 
| 320 | 
            +
                    return if where_args == OPTS || where_args == EMPTY_ARRAY
         | 
| 321 | 
            +
                  end
         | 
| 322 | 
            +
             | 
| 323 | 
            +
                  cached_placeholder_literalizer(key, &block)
         | 
| 324 | 
            +
                end
         | 
| 325 | 
            +
             | 
| 312 326 | 
             
                # Set the columns for the current dataset.
         | 
| 313 327 | 
             
                def columns=(v)
         | 
| 314 328 | 
             
                  cache_set(:_columns, v)
         | 
| @@ -65,12 +65,12 @@ | |
| 65 65 | 
             
            # If you want an easy way to call PostgreSQL array functions and
         | 
| 66 66 | 
             
            # operators, look into the pg_array_ops extension.
         | 
| 67 67 | 
             
            #
         | 
| 68 | 
            -
            # This extension requires the  | 
| 68 | 
            +
            # This extension requires the delegate library, and the strscan library
         | 
| 69 | 
            +
            # sequel_pg has not been loaded.
         | 
| 69 70 | 
             
            #
         | 
| 70 71 | 
             
            # Related module: Sequel::Postgres::PGArray
         | 
| 71 72 |  | 
| 72 73 | 
             
            require 'delegate'
         | 
| 73 | 
            -
            require 'strscan'
         | 
| 74 74 |  | 
| 75 75 | 
             
            module Sequel
         | 
| 76 76 | 
             
              module Postgres
         | 
| @@ -99,7 +99,7 @@ module Sequel | |
| 99 99 | 
             
                        register_array_type('bytea', :oid=>1001, :scalar_oid=>17, :type_symbol=>:blob)
         | 
| 100 100 | 
             
                        register_array_type('date', :oid=>1182, :scalar_oid=>1082)
         | 
| 101 101 | 
             
                        register_array_type('time without time zone', :oid=>1183, :scalar_oid=>1083, :type_symbol=>:time)
         | 
| 102 | 
            -
                        register_array_type('time with time zone', :oid=>1270, :scalar_oid=> | 
| 102 | 
            +
                        register_array_type('time with time zone', :oid=>1270, :scalar_oid=>1266, :type_symbol=>:time_timezone, :scalar_typecast=>:time)
         | 
| 103 103 |  | 
| 104 104 | 
             
                        register_array_type('smallint', :oid=>1005, :scalar_oid=>21, :scalar_typecast=>:integer)
         | 
| 105 105 | 
             
                        register_array_type('oid', :oid=>1028, :scalar_oid=>26, :scalar_typecast=>:integer)
         | 
| @@ -300,93 +300,97 @@ module Sequel | |
| 300 300 | 
             
                    end
         | 
| 301 301 | 
             
                  end
         | 
| 302 302 |  | 
| 303 | 
            -
                   | 
| 304 | 
            -
             | 
| 305 | 
            -
             | 
| 306 | 
            -
             | 
| 307 | 
            -
                    #  | 
| 308 | 
            -
                    #  | 
| 309 | 
            -
                     | 
| 310 | 
            -
             | 
| 311 | 
            -
             | 
| 312 | 
            -
                       | 
| 313 | 
            -
                       | 
| 314 | 
            -
                       | 
| 315 | 
            -
             | 
| 316 | 
            -
             | 
| 317 | 
            -
             | 
| 303 | 
            +
                  unless Sequel::Postgres.respond_to?(:parse_pg_array)
         | 
| 304 | 
            +
                    require 'strscan'
         | 
| 305 | 
            +
             | 
| 306 | 
            +
                    # PostgreSQL array parser that handles PostgreSQL array output format.
         | 
| 307 | 
            +
                    # Note that does not handle all forms out input that PostgreSQL will
         | 
| 308 | 
            +
                    # accept, and it will not raise an error for all forms of invalid input.
         | 
| 309 | 
            +
                    class Parser < StringScanner
         | 
| 310 | 
            +
                      # Set the source for the input, and any converter callable
         | 
| 311 | 
            +
                      # to call with objects to be created.  For nested parsers
         | 
| 312 | 
            +
                      # the source may contain text after the end current parse,
         | 
| 313 | 
            +
                      # which will be ignored.
         | 
| 314 | 
            +
                      def initialize(source, converter=nil)
         | 
| 315 | 
            +
                        super(source)
         | 
| 316 | 
            +
                        @converter = converter 
         | 
| 317 | 
            +
                        @stack = [[]]
         | 
| 318 | 
            +
                        @encoding = string.encoding
         | 
| 319 | 
            +
                        @recorded = String.new.force_encoding(@encoding)
         | 
| 320 | 
            +
                      end
         | 
| 318 321 |  | 
| 319 | 
            -
             | 
| 320 | 
            -
             | 
| 321 | 
            -
             | 
| 322 | 
            -
             | 
| 323 | 
            -
             | 
| 324 | 
            -
             | 
| 325 | 
            -
             | 
| 326 | 
            -
             | 
| 327 | 
            -
             | 
| 322 | 
            +
                      # Take the buffer of recorded characters and add it to the array
         | 
| 323 | 
            +
                      # of entries, and use a new buffer for recorded characters.
         | 
| 324 | 
            +
                      def new_entry(include_empty=false)
         | 
| 325 | 
            +
                        if !@recorded.empty? || include_empty
         | 
| 326 | 
            +
                          entry = @recorded
         | 
| 327 | 
            +
                          if entry == 'NULL' && !include_empty
         | 
| 328 | 
            +
                            entry = nil
         | 
| 329 | 
            +
                          elsif @converter
         | 
| 330 | 
            +
                            entry = @converter.call(entry)
         | 
| 331 | 
            +
                          end
         | 
| 332 | 
            +
                          @stack.last.push(entry)
         | 
| 333 | 
            +
                          @recorded = String.new.force_encoding(@encoding)
         | 
| 328 334 | 
             
                        end
         | 
| 329 | 
            -
                        @stack.last.push(entry)
         | 
| 330 | 
            -
                        @recorded = String.new.force_encoding(@encoding)
         | 
| 331 335 | 
             
                      end
         | 
| 332 | 
            -
                    end
         | 
| 333 336 |  | 
| 334 | 
            -
             | 
| 335 | 
            -
             | 
| 336 | 
            -
             | 
| 337 | 
            -
             | 
| 338 | 
            -
             | 
| 339 | 
            -
             | 
| 340 | 
            -
             | 
| 341 | 
            -
             | 
| 342 | 
            -
             | 
| 343 | 
            -
             | 
| 344 | 
            -
             | 
| 345 | 
            -
             | 
| 346 | 
            -
             | 
| 347 | 
            -
             | 
| 348 | 
            -
             | 
| 349 | 
            -
             | 
| 350 | 
            -
             | 
| 351 | 
            -
             | 
| 352 | 
            -
             | 
| 353 | 
            -
             | 
| 354 | 
            -
             | 
| 337 | 
            +
                      # Parse the input character by character, returning an array
         | 
| 338 | 
            +
                      # of parsed (and potentially converted) objects.
         | 
| 339 | 
            +
                      def parse
         | 
| 340 | 
            +
                        raise Sequel::Error, "invalid array, empty string" if eos?
         | 
| 341 | 
            +
                        raise Sequel::Error, "invalid array, doesn't start with {" unless scan(/((\[\d+:\d+\])+=)?\{/)
         | 
| 342 | 
            +
             | 
| 343 | 
            +
                        while !eos?
         | 
| 344 | 
            +
                          char = scan(/[{}",]|[^{}",]+/)
         | 
| 345 | 
            +
                          if char == ','
         | 
| 346 | 
            +
                            # Comma outside quoted string indicates end of current entry
         | 
| 347 | 
            +
                            new_entry
         | 
| 348 | 
            +
                          elsif char == '"'
         | 
| 349 | 
            +
                            raise Sequel::Error, "invalid array, opening quote with existing recorded data" unless @recorded.empty?
         | 
| 350 | 
            +
                            while true
         | 
| 351 | 
            +
                              char = scan(/["\\]|[^"\\]+/)
         | 
| 352 | 
            +
                              if char == '\\'
         | 
| 353 | 
            +
                                @recorded << getch
         | 
| 354 | 
            +
                              elsif char == '"'
         | 
| 355 | 
            +
                                n = peek(1)
         | 
| 356 | 
            +
                                raise Sequel::Error, "invalid array, closing quote not followed by comma or closing brace" unless n == ',' || n == '}'
         | 
| 357 | 
            +
                                break
         | 
| 358 | 
            +
                              else
         | 
| 359 | 
            +
                                @recorded << char
         | 
| 360 | 
            +
                              end
         | 
| 361 | 
            +
                            end
         | 
| 362 | 
            +
                            new_entry(true)
         | 
| 363 | 
            +
                          elsif char == '{'
         | 
| 364 | 
            +
                            raise Sequel::Error, "invalid array, opening brace with existing recorded data" unless @recorded.empty?
         | 
| 365 | 
            +
             | 
| 366 | 
            +
                            # Start of new array, add it to the stack
         | 
| 367 | 
            +
                            new = []
         | 
| 368 | 
            +
                            @stack.last << new
         | 
| 369 | 
            +
                            @stack << new
         | 
| 370 | 
            +
                          elsif char == '}'
         | 
| 371 | 
            +
                            # End of current array, add current entry to the current array
         | 
| 372 | 
            +
                            new_entry
         | 
| 373 | 
            +
             | 
| 374 | 
            +
                            if @stack.length == 1
         | 
| 375 | 
            +
                              raise Sequel::Error, "array parsing finished without parsing entire string" unless eos?
         | 
| 376 | 
            +
             | 
| 377 | 
            +
                              # Top level of array, parsing should be over.
         | 
| 378 | 
            +
                              # Pop current array off stack and return it as result
         | 
| 379 | 
            +
                              return @stack.pop
         | 
| 355 380 | 
             
                            else
         | 
| 356 | 
            -
                               | 
| 381 | 
            +
                              # Nested array, pop current array off stack
         | 
| 382 | 
            +
                              @stack.pop
         | 
| 357 383 | 
             
                            end
         | 
| 358 | 
            -
                          end
         | 
| 359 | 
            -
                          new_entry(true)
         | 
| 360 | 
            -
                        elsif char == '{'
         | 
| 361 | 
            -
                          raise Sequel::Error, "invalid array, opening brace with existing recorded data" unless @recorded.empty?
         | 
| 362 | 
            -
             | 
| 363 | 
            -
                          # Start of new array, add it to the stack
         | 
| 364 | 
            -
                          new = []
         | 
| 365 | 
            -
                          @stack.last << new
         | 
| 366 | 
            -
                          @stack << new
         | 
| 367 | 
            -
                        elsif char == '}'
         | 
| 368 | 
            -
                          # End of current array, add current entry to the current array
         | 
| 369 | 
            -
                          new_entry
         | 
| 370 | 
            -
             | 
| 371 | 
            -
                          if @stack.length == 1
         | 
| 372 | 
            -
                            raise Sequel::Error, "array parsing finished without parsing entire string" unless eos?
         | 
| 373 | 
            -
             | 
| 374 | 
            -
                            # Top level of array, parsing should be over.
         | 
| 375 | 
            -
                            # Pop current array off stack and return it as result
         | 
| 376 | 
            -
                            return @stack.pop
         | 
| 377 384 | 
             
                          else
         | 
| 378 | 
            -
                            #  | 
| 379 | 
            -
                            @ | 
| 385 | 
            +
                            # Add the character to the recorded character buffer.
         | 
| 386 | 
            +
                            @recorded << char
         | 
| 380 387 | 
             
                          end
         | 
| 381 | 
            -
                        else
         | 
| 382 | 
            -
                          # Add the character to the recorded character buffer.
         | 
| 383 | 
            -
                          @recorded << char
         | 
| 384 388 | 
             
                        end
         | 
| 385 | 
            -
                      end
         | 
| 386 389 |  | 
| 387 | 
            -
             | 
| 390 | 
            +
                        raise Sequel::Error, "array parsing finished with array unclosed"
         | 
| 391 | 
            +
                      end
         | 
| 388 392 | 
             
                    end
         | 
| 389 | 
            -
                  end | 
| 393 | 
            +
                  end
         | 
| 390 394 |  | 
| 391 395 | 
             
                  # Callable object that takes the input string and parses it using Parser.
         | 
| 392 396 | 
             
                  class Creator
         |