sequel 5.106.0 → 5.108.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.
Files changed (131) hide show
  1. checksums.yaml +4 -4
  2. data/bin/sequel +5 -5
  3. data/lib/sequel/adapters/ado.rb +1 -1
  4. data/lib/sequel/adapters/ibmdb.rb +2 -2
  5. data/lib/sequel/adapters/jdbc/hsqldb.rb +1 -1
  6. data/lib/sequel/adapters/jdbc/mysql.rb +1 -1
  7. data/lib/sequel/adapters/jdbc.rb +2 -2
  8. data/lib/sequel/adapters/mock.rb +1 -1
  9. data/lib/sequel/adapters/mysql.rb +7 -2
  10. data/lib/sequel/adapters/mysql2.rb +5 -4
  11. data/lib/sequel/adapters/postgres.rb +37 -37
  12. data/lib/sequel/adapters/shared/db2.rb +2 -2
  13. data/lib/sequel/adapters/shared/mssql.rb +4 -4
  14. data/lib/sequel/adapters/shared/mysql.rb +2 -2
  15. data/lib/sequel/adapters/shared/postgres.rb +997 -38
  16. data/lib/sequel/adapters/shared/sqlite.rb +8 -7
  17. data/lib/sequel/adapters/utils/emulate_offset_with_reverse_and_count.rb +1 -1
  18. data/lib/sequel/adapters/utils/mysql_mysql2.rb +11 -1
  19. data/lib/sequel/connection_pool/sharded_single.rb +1 -1
  20. data/lib/sequel/connection_pool/sharded_threaded.rb +7 -5
  21. data/lib/sequel/connection_pool/sharded_timed_queue.rb +3 -3
  22. data/lib/sequel/connection_pool/threaded.rb +9 -7
  23. data/lib/sequel/connection_pool/timed_queue.rb +2 -2
  24. data/lib/sequel/connection_pool.rb +3 -3
  25. data/lib/sequel/core.rb +30 -22
  26. data/lib/sequel/database/connecting.rb +1 -1
  27. data/lib/sequel/database/features.rb +1 -1
  28. data/lib/sequel/database/misc.rb +7 -7
  29. data/lib/sequel/database/query.rb +2 -2
  30. data/lib/sequel/database/schema_generator.rb +7 -5
  31. data/lib/sequel/database/schema_methods.rb +2 -2
  32. data/lib/sequel/database/transactions.rb +4 -4
  33. data/lib/sequel/dataset/actions.rb +1 -1
  34. data/lib/sequel/dataset/features.rb +4 -4
  35. data/lib/sequel/dataset/graph.rb +1 -1
  36. data/lib/sequel/dataset/misc.rb +3 -3
  37. data/lib/sequel/dataset/prepared_statements.rb +2 -2
  38. data/lib/sequel/dataset/query.rb +8 -8
  39. data/lib/sequel/dataset/sql.rb +18 -10
  40. data/lib/sequel/dataset.rb +2 -2
  41. data/lib/sequel/deprecated.rb +2 -2
  42. data/lib/sequel/exceptions.rb +2 -2
  43. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  44. data/lib/sequel/extensions/async_thread_pool.rb +3 -3
  45. data/lib/sequel/extensions/auto_cast_date_and_time.rb +2 -2
  46. data/lib/sequel/extensions/blank.rb +2 -2
  47. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  48. data/lib/sequel/extensions/core_refinements.rb +8 -8
  49. data/lib/sequel/extensions/date_arithmetic.rb +1 -1
  50. data/lib/sequel/extensions/datetime_parse_to_time.rb +2 -2
  51. data/lib/sequel/extensions/duplicate_columns_handler.rb +2 -2
  52. data/lib/sequel/extensions/graph_each.rb +1 -1
  53. data/lib/sequel/extensions/index_caching.rb +1 -1
  54. data/lib/sequel/extensions/inflector.rb +5 -5
  55. data/lib/sequel/extensions/is_distinct_from.rb +2 -2
  56. data/lib/sequel/extensions/lit_require_frozen.rb +3 -3
  57. data/lib/sequel/extensions/migration.rb +21 -7
  58. data/lib/sequel/extensions/named_timezones.rb +8 -8
  59. data/lib/sequel/extensions/pg_array.rb +9 -8
  60. data/lib/sequel/extensions/pg_array_ops.rb +69 -14
  61. data/lib/sequel/extensions/pg_auto_parameterize.rb +2 -2
  62. data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +3 -3
  63. data/lib/sequel/extensions/pg_enum.rb +2 -2
  64. data/lib/sequel/extensions/pg_extended_date_support.rb +5 -5
  65. data/lib/sequel/extensions/pg_extended_integer_support.rb +1 -1
  66. data/lib/sequel/extensions/pg_hstore.rb +2 -2
  67. data/lib/sequel/extensions/pg_hstore_ops.rb +5 -5
  68. data/lib/sequel/extensions/pg_inet.rb +2 -2
  69. data/lib/sequel/extensions/pg_inet_ops.rb +2 -2
  70. data/lib/sequel/extensions/pg_interval.rb +6 -6
  71. data/lib/sequel/extensions/pg_json.rb +4 -4
  72. data/lib/sequel/extensions/pg_json_ops.rb +11 -11
  73. data/lib/sequel/extensions/pg_range.rb +6 -6
  74. data/lib/sequel/extensions/pg_range_ops.rb +7 -7
  75. data/lib/sequel/extensions/pg_row.rb +4 -4
  76. data/lib/sequel/extensions/pg_row_ops.rb +6 -6
  77. data/lib/sequel/extensions/provenance.rb +2 -2
  78. data/lib/sequel/extensions/query.rb +2 -2
  79. data/lib/sequel/extensions/s.rb +2 -2
  80. data/lib/sequel/extensions/schema_dumper.rb +4 -4
  81. data/lib/sequel/extensions/sequel_4_dataset_methods.rb +2 -0
  82. data/lib/sequel/extensions/sql_comments.rb +1 -1
  83. data/lib/sequel/extensions/sql_log_normalizer.rb +1 -1
  84. data/lib/sequel/extensions/sqlite_json_ops.rb +2 -2
  85. data/lib/sequel/extensions/string_agg.rb +5 -5
  86. data/lib/sequel/extensions/symbol_aref.rb +3 -3
  87. data/lib/sequel/extensions/symbol_aref_refinement.rb +3 -3
  88. data/lib/sequel/extensions/symbol_as_refinement.rb +2 -2
  89. data/lib/sequel/extensions/transaction_connection_validator.rb +1 -1
  90. data/lib/sequel/model/associations.rb +17 -17
  91. data/lib/sequel/model/base.rb +19 -11
  92. data/lib/sequel/model/inflections.rb +1 -1
  93. data/lib/sequel/model/plugins.rb +2 -2
  94. data/lib/sequel/model.rb +2 -2
  95. data/lib/sequel/plugins/association_pks.rb +1 -1
  96. data/lib/sequel/plugins/association_proxies.rb +4 -4
  97. data/lib/sequel/plugins/auto_restrict_eager_graph.rb +2 -0
  98. data/lib/sequel/plugins/caching.rb +1 -1
  99. data/lib/sequel/plugins/class_table_inheritance.rb +1 -1
  100. data/lib/sequel/plugins/column_encryption.rb +10 -10
  101. data/lib/sequel/plugins/concurrent_eager_loading.rb +2 -2
  102. data/lib/sequel/plugins/csv_serializer.rb +2 -2
  103. data/lib/sequel/plugins/deprecated_associations.rb +2 -2
  104. data/lib/sequel/plugins/eager_each.rb +1 -1
  105. data/lib/sequel/plugins/finder.rb +1 -1
  106. data/lib/sequel/plugins/forbid_lazy_load.rb +3 -3
  107. data/lib/sequel/plugins/insert_conflict.rb +1 -1
  108. data/lib/sequel/plugins/insert_returning_select.rb +1 -1
  109. data/lib/sequel/plugins/json_serializer.rb +4 -4
  110. data/lib/sequel/plugins/lazy_attributes.rb +1 -1
  111. data/lib/sequel/plugins/many_through_many.rb +1 -1
  112. data/lib/sequel/plugins/pg_array_associations.rb +4 -4
  113. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +3 -3
  114. data/lib/sequel/plugins/pg_eager_any_typed_array.rb +2 -2
  115. data/lib/sequel/plugins/prepared_statements.rb +2 -2
  116. data/lib/sequel/plugins/serialization.rb +1 -1
  117. data/lib/sequel/plugins/sharding.rb +1 -1
  118. data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
  119. data/lib/sequel/plugins/skip_saving_columns.rb +2 -2
  120. data/lib/sequel/plugins/split_values.rb +1 -1
  121. data/lib/sequel/plugins/sql_comments.rb +8 -8
  122. data/lib/sequel/plugins/subclasses.rb +6 -6
  123. data/lib/sequel/plugins/subset_conditions.rb +2 -2
  124. data/lib/sequel/plugins/tactical_eager_loading.rb +1 -1
  125. data/lib/sequel/plugins/throw_failures.rb +2 -2
  126. data/lib/sequel/plugins/unused_associations.rb +5 -5
  127. data/lib/sequel/plugins/validation_helpers.rb +1 -1
  128. data/lib/sequel/plugins/xml_serializer.rb +2 -0
  129. data/lib/sequel/sql.rb +11 -11
  130. data/lib/sequel/version.rb +1 -1
  131. metadata +2 -2
@@ -204,12 +204,12 @@ module Sequel
204
204
  @in = v
205
205
  end
206
206
 
207
- # Assumes hash partitioning, sets the modulus for this parition.
207
+ # Assumes hash partitioning, sets the modulus for this partition.
208
208
  def modulus(v)
209
209
  @modulus = v
210
210
  end
211
211
 
212
- # Assumes hash partitioning, sets the remainder for this parition.
212
+ # Assumes hash partitioning, sets the remainder for this partition.
213
213
  def remainder(v)
214
214
  @remainder = v
215
215
  end
@@ -256,6 +256,564 @@ module Sequel
256
256
  end
257
257
  end
258
258
 
259
+ module PropertyGraph
260
+ # Base class for all Generator DSL classes. This uses a design where
261
+ # The DSL class is only used for the evaluation of the block, and new
262
+ # returns a frozen struct.
263
+ class Generator
264
+ # Instead of returning the Generator instance, return a frozen struct
265
+ # with data from the generator. This prevents accidentally calling the
266
+ # generator methods, and makes it possible for the generator class and
267
+ # result class to use the same method name in two different ways, with
268
+ # the generator setting data and the frozen struct method returning it.
269
+ # The frozen struct classes use the constant Data under each generator
270
+ # subclass.
271
+ def self.new(*args, &block)
272
+ super(*args, &block).data
273
+ end
274
+
275
+ # Base class for Vertex and Edge.
276
+ class Element < self
277
+ Data = Struct.new(:name, :key, :labels)
278
+
279
+ # +name+ specifies the name of the vertex or edge. It can be an
280
+ # SQL::AliasedExpression to use an alias. Options:
281
+ # :properties :: Specifies fixed properties for the vertex or edge.
282
+ # If this is given, you cannot use the label method
283
+ # inside the block.
284
+ def initialize(name, opts=OPTS, &block)
285
+ @name = name
286
+ @labels = []
287
+ if opts.key?(:properties)
288
+ @labels << [nil, opts[:properties]].freeze
289
+ @labels.freeze
290
+ end
291
+ instance_exec(&block) if block
292
+ @labels.freeze
293
+ freeze
294
+ end
295
+
296
+ def data
297
+ Data.new(@name, @key, @labels).freeze
298
+ end
299
+
300
+ # Set the column(s) to use for the KEY clause, which are the columns
301
+ # that uniquely identify rows in the table:
302
+ #
303
+ # key(:id)
304
+ # # KEY (id)
305
+ #
306
+ # key([:id1, :id2])
307
+ # # KEY (id1, id2)
308
+ def key(columns)
309
+ @key = Array(columns)
310
+ end
311
+
312
+ # Add a label and properties for the label for this vertex/edge.
313
+ # A vertex or edge can have multiple labels with separate properties,
314
+ # if it wasn't created with fixed properties. The +name+ argument
315
+ # specifies the label name. The +properties+ argument specifies the
316
+ # properties:
317
+ # nil, :all :: PROPERTIES ALL COLUMNS
318
+ # false, :none, [] :: NO PROPERTIES
319
+ # Array :: Array of specific properties. Each element should be a Symbol,
320
+ # SQL::Identifier, or SQL::AliasedExpression.
321
+ #
322
+ # label(:label_name)
323
+ # # LABEL label_name PROPERTIES ALL COLUMNS
324
+ #
325
+ # label(:label_name, [])
326
+ # # LABEL label_name NO PROPERTIES
327
+ #
328
+ # label(:label_name, [:c, Sequel[:b].as(:d)], Sequel[:e])
329
+ # # LABEL label_name PROPERTIES (c, b AS d, e)
330
+ def label(name, properties=:all)
331
+ if @labels.frozen?
332
+ raise Error, "cannot specify label for property graph vertex or edge with fixed properties"
333
+ end
334
+ @labels << [name, properties].freeze
335
+ nil
336
+ end
337
+ end
338
+
339
+ # Vertex is used for the block passed to Create#vertex, used to configure
340
+ # vertices in the property graph. It doesn't have any additional behavior
341
+ # compared to the Element class, so this is an alias instead of a subclass.
342
+ Vertex = Element
343
+
344
+ # Target is used for the block passed to Edge#source and Edge#destination,
345
+ # used to configure the source and destination of property graph edges.
346
+ class Target < self
347
+ Data = Struct.new(:name, :key, :references)
348
+
349
+ # +name+ specifies the name of the source or destination.
350
+ def initialize(name, &block)
351
+ @name = name
352
+ @key = nil
353
+ @references = nil
354
+ instance_exec(&block) if block
355
+ freeze
356
+ end
357
+
358
+ def data
359
+ Data.new(@name, @key, @references).freeze
360
+ end
361
+
362
+ # Set the column(s) to use for the KEY clause, which are the columns
363
+ # in the edge table that reference columns in the source or destination.
364
+ # Should be combined with #references to specify the columns being
365
+ # referenced.
366
+ #
367
+ # key(:vertex_id)
368
+ # # KEY (vertex_id)
369
+ #
370
+ # key([:vertex_id1, :vertex_id2])
371
+ # # KEY (vertex_id1, vertex_id2)
372
+ def key(keys)
373
+ @key = Array(keys)
374
+ end
375
+
376
+ # Set the column(s) to use for the REFERENCES clause, which are the columns
377
+ # in the source or destination table that are referenced by the edge table.
378
+ # Should be combined with #key to specify the columns doing the referencing.
379
+ #
380
+ # references(:id)
381
+ # # REFERENCES (id)
382
+ #
383
+ # references([:id1, :id2])
384
+ # # REFERENCES (id1, id2)
385
+ def references(refs)
386
+ @references = Array(refs)
387
+ end
388
+ end
389
+
390
+ # Edge is used for block passed to Create#edge, used to configure edges
391
+ # in the property graph.
392
+ class Edge < Element
393
+ Data = Struct.new(:name, :key, :labels, :source, :destination)
394
+
395
+ # In addition to inherited behavior, raises an error if a block
396
+ # is not passed or source or destination is not called in the block.
397
+ def initialize(name, opts=OPTS, &block)
398
+ super
399
+
400
+ unless @source && @destination
401
+ raise Error, "source and/or destination not defined for property graph edge"
402
+ end
403
+ end
404
+
405
+ def data
406
+ Data.new(@name, @key, @labels, @source, @destination).freeze
407
+ end
408
+
409
+ # Specify the source for the edge, with block evaluated by Target.
410
+ def source(name, &block)
411
+ raise Error, "cannot specify multiple sources for a property graph edge" if @source
412
+ @source = Target.new(name, &block)
413
+ end
414
+
415
+ # Specify the destination for the edge, with block evaluated by Target.
416
+ def destination(name, &block)
417
+ raise Error, "cannot specify multiple destinations for a property graph edge" if @destination
418
+ @destination = Target.new(name, &block)
419
+ end
420
+ end
421
+
422
+ # Create is used to evaluate the block given to DatabaseMethods#create_property_graph,
423
+ # used to specify the vertices and edges in the property graph.
424
+ class Create < self
425
+ Data = Struct.new(:vertices, :edges)
426
+
427
+ def initialize(&block)
428
+ @vertices = []
429
+ @edges = []
430
+ instance_exec(&block)
431
+ @vertices.freeze
432
+ @edges.freeze
433
+ freeze
434
+ end
435
+
436
+ def data
437
+ Data.new(@vertices, @edges).freeze
438
+ end
439
+
440
+ # Adds a vertex to the property graph, with the block evaluated by Vertex.
441
+ def vertex(name, opts=OPTS, &block)
442
+ @vertices << Vertex.new(name, opts, &block)
443
+ end
444
+
445
+ # Adds an edge to the property graph, with the block evaluated by Edge.
446
+ def edge(name, opts=OPTS, &block)
447
+ @edges << Edge.new(name, opts, &block)
448
+ end
449
+ end
450
+
451
+ # AlterElement is used to evaluate the block passed to
452
+ # Alter#alter_vertex_table and Alter#alter_edge_table.
453
+ class AlterElement < self
454
+ # +kind+ is +:vertex+ or +:edge+. +name+ is the alias of the
455
+ # vertex or edge table to alter.
456
+ def initialize(kind, name, &block)
457
+ @kind = kind
458
+ @name = name
459
+ @labels = []
460
+ @operations = []
461
+ instance_exec(&block)
462
+
463
+ # All labels added via #add_label are combined into a single
464
+ # ADD LABEL operation, as PostgreSQL supports adding multiple
465
+ # labels in a single ALTER ... ADD LABEL statement.
466
+ unless @labels.empty?
467
+ @operations << {:op=>:add_label, :kind=>kind, :name=>name, :labels=>@labels.freeze}
468
+ end
469
+
470
+ @operations.each(&:freeze)
471
+ @operations.freeze
472
+ freeze
473
+ end
474
+
475
+ def data
476
+ @operations
477
+ end
478
+
479
+ # Add a label (and optional properties) to the vertex/edge table.
480
+ # Takes the same arguments as Element#label. Can be called multiple
481
+ # times to add multiple labels.
482
+ #
483
+ # add_label(:l)
484
+ # # ADD LABEL l PROPERTIES ALL COLUMNS
485
+ def add_label(name, properties=:all)
486
+ @labels << [name, properties].freeze
487
+ nil
488
+ end
489
+
490
+ # Remove a label from the vertex/edge table. Options:
491
+ # :cascade :: Use CASCADE to drop dependent objects.
492
+ #
493
+ # drop_label(:l)
494
+ # # DROP LABEL l
495
+ def drop_label(name, opts=OPTS)
496
+ @operations << {:op=>:drop_label, :kind=>@kind, :name=>@name, :label=>name, :cascade=>opts[:cascade]}
497
+ nil
498
+ end
499
+
500
+ # Add properties to an existing label on the vertex/edge table.
501
+ # +properties+ is an expression, or array of expressions, the same
502
+ # as the explicit array form of the +properties+ argument to
503
+ # Element#label.
504
+ #
505
+ # add_properties(:l, [:c1, Sequel[:c2].as(:c3)])
506
+ # # ALTER LABEL l ADD PROPERTIES (c1, c2 AS c3)
507
+ def add_properties(label, properties)
508
+ @operations << {:op=>:add_properties, :kind=>@kind, :name=>@name, :label=>label, :properties=>Array(properties)}
509
+ nil
510
+ end
511
+
512
+ # Remove properties from an existing label on the vertex/edge table.
513
+ # +properties+ is a column name, or array of column names. Options:
514
+ # :cascade :: Use CASCADE to drop dependent objects.
515
+ #
516
+ # drop_properties(:l, [:c1])
517
+ # # ALTER LABEL l DROP PROPERTIES (c1)
518
+ def drop_properties(label, properties, opts=OPTS)
519
+ @operations << {:op=>:drop_properties, :kind=>@kind, :name=>@name, :label=>label, :properties=>Array(properties), :cascade=>opts[:cascade]}
520
+ nil
521
+ end
522
+ end
523
+
524
+ # Alter is used to evaluate the block given to DatabaseMethods#alter_property_graph,
525
+ # used to specify changes to an existing property graph.
526
+ class Alter < self
527
+ def initialize(&block)
528
+ @operations = []
529
+ instance_exec(&block)
530
+
531
+ @operations.each do |op|
532
+ case op[:op]
533
+ when :add_vertex_tables, :add_edge_tables
534
+ op[:tables].freeze
535
+ end
536
+ op.freeze
537
+ end
538
+ @operations.freeze
539
+ freeze
540
+ end
541
+
542
+ def data
543
+ @operations
544
+ end
545
+
546
+ # Add a vertex to the property graph, with the block used to configure the
547
+ # vertex.
548
+ #
549
+ # alter_property_graph.add_vertex(:v)
550
+ # # ADD VERTEX TABLES (v)
551
+ def add_vertex(name, opts=OPTS, &block)
552
+ add_tables_operation(:add_vertex_tables) << Vertex.new(name, opts, &block)
553
+ nil
554
+ end
555
+
556
+ # Add an edge to the property graph, with the block used to configure the edge.
557
+ #
558
+ # alter_property_graph.add_edge(:e){source :v1; destination :v2}
559
+ # # ADD EDGE TABLES (e SOURCE v1 DESTINATION v2)
560
+ def add_edge(name, opts=OPTS, &block)
561
+ add_tables_operation(:add_edge_tables) << Edge.new(name, opts, &block)
562
+ nil
563
+ end
564
+
565
+ # Remove vertex tables (referenced by their aliases) from the
566
+ # property graph. +aliases+ can be a single alias or an array.
567
+ # Options:
568
+ # :cascade :: Use CASCADE instead of the default RESTRICT.
569
+ #
570
+ # alter_property_graph.drop_vertex_tables([:v1, :v2])
571
+ # # DROP VERTEX TABLES (v1, v2)
572
+ def drop_vertex_tables(aliases, opts=OPTS)
573
+ @operations << {:op=>:drop_vertex_tables, :aliases=>Array(aliases), :cascade=>opts[:cascade]}
574
+ nil
575
+ end
576
+
577
+ # Remove edge tables (referenced by their aliases) from the property
578
+ # graph. See #drop_vertex_tables.
579
+ #
580
+ # alter_property_graph.drop_edge_tables([:e1, :e2])
581
+ # # DROP EDGE TABLES (e1, e2)
582
+ def drop_edge_tables(aliases, opts=OPTS)
583
+ @operations << {:op=>:drop_edge_tables, :aliases=>Array(aliases), :cascade=>opts[:cascade]}
584
+ nil
585
+ end
586
+
587
+ # Modify an existing vertex table (referenced by its alias).
588
+ #
589
+ # alter_property_graph.alter_vertex_table(:v){add_label :l}
590
+ # # ALTER VERTEX TABLE v ADD LABEL l PROPERTIES ALL COLUMNS
591
+ def alter_vertex_table(name, &block)
592
+ @operations.concat(AlterElement.new(:vertex, name, &block))
593
+ nil
594
+ end
595
+
596
+ # Modify an existing edge table (referenced by its alias).
597
+ #
598
+ # alter_property_graph.alter_edge_table(:e, properties: :none){drop_label :l}
599
+ # # ALTER VERTEX TABLE e DROP LABEL l
600
+ def alter_edge_table(name, &block)
601
+ @operations.concat(AlterElement.new(:edge, name, &block))
602
+ nil
603
+ end
604
+
605
+ # Change the owner of the property graph. +new_owner+ is usually a
606
+ # Symbol or SQL::Identifier for the role name, but can be
607
+ # <tt>Sequel.lit('CURRENT_USER')</tt> or
608
+ # <tt>Sequel.lit('SESSION_USER')</tt>.
609
+ #
610
+ # alter_property_graph.owner_to(:new_owner)
611
+ # # OWNER TO new_owner
612
+ def set_owner(new_owner)
613
+ @operations << {:op=>:set_owner, :owner=>new_owner}
614
+ nil
615
+ end
616
+
617
+ private
618
+
619
+ # Internals of add_vertex and add_edge.
620
+ def add_tables_operation(op_name)
621
+ unless op = @operations.find{|o| o[:op] == op_name}
622
+ @operations << (op = {:op=>op_name, :tables=>[]})
623
+ end
624
+ op[:tables]
625
+ end
626
+ end
627
+ end
628
+
629
+ # Represents a GRAPH_TABLE expression, used to query a property graph
630
+ # via graph pattern matching. This is used in place of a table name
631
+ # expression or dataset in a SELECT query. These are created by calling
632
+ # #graph_table on the related Database object.
633
+ #
634
+ # Table uses a method chaining design, similar to Dataset, where methods
635
+ # return modified frozen copies of the object.
636
+ class Table
637
+ include SQL::AliasMethods
638
+
639
+ # Internal struct for a single element (vertex or edge) in the graph pattern:
640
+ # +type+ :: Either :vertex or :edge.
641
+ # +marker+ :: Connector string to use for the element (empty for initial vertex).
642
+ # +label+ :: Label restriction symbol or SQL::Identifier for the element, if any.
643
+ # Can be an array or set to match multiple labels.
644
+ # +var+ :: Graph pattern variable symbol for the element, if any.
645
+ # +where+ :: WHERE condition for the element, if any.
646
+ Element = Struct.new(:type, :marker, :label, :var, :where) do
647
+ # Method used to create elements, used instead of new
648
+ # to ensure that the returned elements are frozen.
649
+ def self.create(type, marker, label, opts)
650
+ case label
651
+ when Array, Set
652
+ label = label.dup.freeze unless label.frozen?
653
+ end
654
+
655
+ case where = opts[:where]
656
+ when Hash, Array
657
+ where = SQL::BooleanExpression.from_value_pairs(where)
658
+ end
659
+
660
+ new(type, marker, label, opts[:var], where).freeze
661
+ end
662
+
663
+ private_class_method :new
664
+ end
665
+ private_constant :Element
666
+
667
+ # The name of the property graph the table is querying.
668
+ attr_reader :name
669
+
670
+ # A frozen array of Element instances, representing the vertices and
671
+ # edges in the graph pattern.
672
+ attr_reader :elements
673
+
674
+ # A frozen array of the columns used in the COLUMNS clause (aliased
675
+ # as columns_used, as #columns is used to modify the columns).
676
+ attr_reader :columns
677
+ alias columns_used columns
678
+
679
+ # Create a new Table with the given +graph_name+, with +initial_vertex_label+
680
+ # and +initial_vertex_opts+ being used to create the initial vertex.
681
+ # See Table#link for which options are supported for the initial vertex.
682
+ def self.create(graph_name, initial_vertex_label, initial_vertex_opts)
683
+ vertex = Element.create(:vertex, "", initial_vertex_label, initial_vertex_opts)
684
+ new(graph_name, [vertex].freeze, [].freeze)
685
+ end
686
+
687
+ def initialize(name, elements, columns)
688
+ @name = name
689
+ @elements = elements
690
+ @columns = columns
691
+ freeze
692
+ end
693
+
694
+ # Return a modified copy with an element added using a bidirectional link
695
+ # (<tt>-</tt> in the graph pattern).
696
+ # +label+ specifies the label restriction for the element. This can be
697
+ # nil for no label restriction, or an array or set to restrict to the
698
+ # given labels.
699
+ #
700
+ # Options supported:
701
+ # +:var+ :: Specifies a graph pattern variable name for the element,
702
+ # usable in the WHERE or COLUMNS clauses.
703
+ # +:vertex+ :: Specifies that the element being linked to is a vertex.
704
+ # This allows for direct vertex<->vertex linking, instead of
705
+ # the default vertex<->edge<->vertex linking.
706
+ # +:where+ :: An expression to use for the WHERE clause for the element.
707
+ #
708
+ # DB.graph_table(:gn, :v).link(:e)
709
+ # # GRAPH_TABLE (gn MATCH (IS v)-[IS e])
710
+ def link(label, opts=OPTS)
711
+ append_element('-', label, opts)
712
+ end
713
+
714
+ # Similar to #link, but uses a directed link from the previous element
715
+ # to the new element (<tt>-></tt> in the graph pattern). Accepts same
716
+ # arguments and options as #link.
717
+ #
718
+ # DB.graph_table(:gn, :v).to(:e)
719
+ # # GRAPH_TABLE (gn MATCH (IS v)->[IS e])
720
+ def to(label, opts=OPTS)
721
+ append_element('->', label, opts)
722
+ end
723
+
724
+ # Similar to #link, but uses a directed link from the new element
725
+ # to the previous element (<tt><-</tt> in the graph pattern). Accepts
726
+ # same arguments and options as #link.
727
+ #
728
+ # DB.graph_table(:gn, :v).from(:e)
729
+ # # GRAPH_TABLE (gn MATCH (IS v)<-[IS e])
730
+ def from(label, opts=OPTS)
731
+ append_element('<-', label, opts)
732
+ end
733
+
734
+ # Return a modifies copy that uses the given columns. A graph table
735
+ # must have a least one column set before it is used in a query.
736
+ #
737
+ # DB.graph_table(:gn, :v).columns(:a, Sequel[:b].as(:c))
738
+ # # GRAPH_TABLE (gn MATCH (IS v) COLUMNS (a, b AS c))
739
+ def columns(*cols)
740
+ self.class.new(@name, @elements, cols.freeze)
741
+ end
742
+
743
+ # Return a modified copy that adds the given columns to the existing
744
+ # list of columns for the graph table.
745
+ def add_columns(*cols)
746
+ columns(*@columns, *cols)
747
+ end
748
+
749
+ # Append the SQL for the GRAPH_TABLE expression to the given SQL string.
750
+ # Requires graph table have at least one column set.
751
+ def sql_literal_append(ds, sql)
752
+ if @columns.empty?
753
+ raise Error, "cannot use graph_table in a query if it does not return any columns"
754
+ end
755
+ if @elements.last.type == :edge
756
+ raise Error, "cannot use graph_table in a query if the last element is an edge"
757
+ end
758
+
759
+ sql << "GRAPH_TABLE ("
760
+ ds.literal_append(sql, @name)
761
+ sql << " MATCH "
762
+
763
+ @elements.each do |element|
764
+ marker = element.marker
765
+ var = element.var
766
+ label = element.label
767
+ where = element.where
768
+ vertex = element.type == :vertex
769
+
770
+ sql << marker
771
+ sql << (vertex ? '(' : '[')
772
+
773
+ ds.literal_append(sql, var) if var
774
+ if label
775
+ sql << (var ? " IS " : "IS ")
776
+ if label.is_a?(Array)
777
+ label_sep = ""
778
+ label.each do |l|
779
+ sql << label_sep
780
+ label_sep = "|" if label_sep.empty?
781
+ ds.literal_append(sql, l)
782
+ end
783
+ else
784
+ ds.literal_append(sql, label)
785
+ end
786
+ end
787
+
788
+ if where
789
+ sql << ((var || label) ? " WHERE " : "WHERE ")
790
+ ds.literal_append(sql, where)
791
+ end
792
+
793
+ sql << (vertex ? ')' : ']')
794
+ end
795
+
796
+ sql << " COLUMNS "
797
+ ds.literal_append(sql, @columns)
798
+ sql << ")"
799
+ end
800
+
801
+ private
802
+
803
+ # Internals of #link, #to, and #from.
804
+ def append_element(marker, label, opts)
805
+ node_type = if opts[:vertex]
806
+ :vertex
807
+ else
808
+ @elements.last.type == :vertex ? :edge : :vertex
809
+ end
810
+
811
+ element = Element.create(node_type, marker, label, opts)
812
+ self.class.new(@name, (@elements.dup << element).freeze, @columns)
813
+ end
814
+ end
815
+ end
816
+
259
817
  # Error raised when Sequel determines a PostgreSQL exclusion constraint has been violated.
260
818
  class ExclusionConstraintViolation < Sequel::ConstraintViolation; end
261
819
 
@@ -338,6 +896,64 @@ module Sequel
338
896
  add_conversion_proc(oid, block)
339
897
  end
340
898
 
899
+ # Alter the property graph with the given +name+, supported on PostgreSQL 19+.
900
+ # The block uses a DSL, evaluated by PropertyGraph::Generator::Alter. Example:
901
+ #
902
+ # DB.alter_property_graph(:my_graph) do
903
+ # # PropertyGraph::Generator::Alter
904
+ # add_vertex :companies2
905
+ # # ALTER PROPERTY GRAPH "my_graph" ADD VERTEX TABLES ("companies2")
906
+ #
907
+ # add_edge :works_at2 do
908
+ # # PropertyGraph::Generator::Edge
909
+ # source :people
910
+ # destination :companies2
911
+ # end
912
+ # # ALTER PROPERTY GRAPH "my_graph" ADD EDGE TABLES
913
+ # # ("works_at2" SOURCE "people" DESTINATION "companies2")
914
+ #
915
+ # drop_vertex_tables [:p2], cascade: true
916
+ # # ALTER PROPERTY GRAPH "my_graph" DROP VERTEX TABLES ("p2") CASCADE
917
+ #
918
+ # drop_edge_tables :e2
919
+ # # ALTER PROPERTY GRAPH "my_graph" DROP EDGE TABLES ("e2")
920
+ #
921
+ # alter_vertex_table :companies do
922
+ # # PropertyGraph::Generator::AlterElement
923
+ # add_label :public_company, [:name, :symbol]
924
+ # # ALTER PROPERTY GRAPH "my_graph" ALTER VERTEX TABLE "companies"
925
+ # # ADD LABEL "public_company" PROPERTIES ("name", "symbol")
926
+ #
927
+ # drop_label :private_company
928
+ # # ALTER PROPERTY GRAPH "my_graph" ALTER VERTEX TABLE "companies"
929
+ # # DROP LABEL "private_company"
930
+ #
931
+ # add_properties :company, :revenue
932
+ # # ALTER PROPERTY GRAPH "my_graph" ALTER VERTEX TABLE "companies"
933
+ # # ALTER LABEL "company" ADD PROPERTIES ("revenue")
934
+ #
935
+ # drop_properties :company, :internal_id, cascade: true
936
+ # # ALTER PROPERTY GRAPH "my_graph" ALTER VERTEX TABLE "companies"
937
+ # # ALTER LABEL "company" DROP PROPERTIES ("internal_id") CASCADE
938
+ # end
939
+ #
940
+ # alter_edge_table :works_at do
941
+ # # PropertyGraph::Generator::AlterElement
942
+ # add_label :employment
943
+ # end
944
+ # # ALTER PROPERTY GRAPH "my_graph" ALTER EDGE TABLE "works_at"
945
+ # # ADD LABEL "employment" PROPERTIES ALL COLUMNS
946
+ #
947
+ # owner_to :new_owner
948
+ # # ALTER PROPERTY GRAPH "my_graph" OWNER TO "new_owner"
949
+ # end
950
+ def alter_property_graph(name, &block)
951
+ PropertyGraph::Generator::Alter.new(&block).each do |op|
952
+ execute_ddl(alter_property_graph_op_sql(name, op).freeze)
953
+ end
954
+ nil
955
+ end
956
+
341
957
  def commit_prepared_transaction(transaction_id, opts=OPTS)
342
958
  run("COMMIT PREPARED #{literal(transaction_id)}".freeze, opts)
343
959
  end
@@ -346,7 +962,7 @@ module Sequel
346
962
  # Keys are CHECK constraint name symbols. Values are hashes with the following keys:
347
963
  # :definition :: An SQL fragment for the definition of the constraint
348
964
  # :columns :: An array of column symbols for the columns referenced in the constraint,
349
- # can be an empty array if the database cannot deteremine the column symbols.
965
+ # can be an empty array if the database cannot determine the column symbols.
350
966
  def check_constraints(table)
351
967
  m = output_identifier_meth
352
968
 
@@ -467,6 +1083,67 @@ module Sequel
467
1083
  self << create_language_sql(name, opts).freeze
468
1084
  end
469
1085
 
1086
+ # Create a property graph in the database, supported on PostgreSQL 19+.
1087
+ #
1088
+ # Arguments:
1089
+ # name :: Name of the property graph
1090
+ # opts :: options hash:
1091
+ # :temp :: Create the property graph as a temporary property graph.
1092
+ #
1093
+ # The block uses a DSL, with classes under PropertyGraph::Generator:
1094
+ #
1095
+ # DB.create_property_graph(:my_graph) do
1096
+ # # PropertyGraph::Generator::Create
1097
+ # vertex :people
1098
+ #
1099
+ # vertex Sequel.as(:people, :p), properties: []
1100
+ #
1101
+ # vertex Sequel.as(:companies, :c) do
1102
+ # # PropertyGraph::Generator::Vertex
1103
+ # key :id
1104
+ # label :company
1105
+ # label :c, [:name, (Sequel[:revenue] / 1000).as(:revenue_thousands)]
1106
+ # end
1107
+ #
1108
+ # edge :works_at do
1109
+ # # PropertyGraph::Generator::Edge
1110
+ # source :people
1111
+ # destination :c
1112
+ # end
1113
+ #
1114
+ # edge Sequel.as(:employment, :e) do
1115
+ # source :people do
1116
+ # # PropertyGraph::Generator::Target
1117
+ # key :person_id
1118
+ # references :id
1119
+ # end
1120
+ # destination :c do
1121
+ # # PropertyGraph::Generator::Target
1122
+ # key :company_id
1123
+ # references :id
1124
+ # end
1125
+ # label :employment
1126
+ # end
1127
+ # end
1128
+ # # CREATE PROPERTY GRAPH "my_graph"
1129
+ # # VERTEX TABLES (
1130
+ # # "people",
1131
+ # # "people" AS "p" NO PROPERTIES,
1132
+ # # "companies" AS "c" KEY ("id")
1133
+ # # LABEL "company" PROPERTIES ALL COLUMNS
1134
+ # # LABEL "c" PROPERTIES ("name", ("revenue" / 1000) AS "revenue_thousands"))
1135
+ # # EDGE TABLES (
1136
+ # # "works_at"
1137
+ # # SOURCE "people"
1138
+ # # DESTINATION "c",
1139
+ # # "employment" AS "e"
1140
+ # # SOURCE KEY ("person_id") REFERENCES "people" ("id")
1141
+ # # DESTINATION KEY ("company_id") REFERENCES "c" ("id")
1142
+ # # LABEL "employment" PROPERTIES ALL COLUMNS)
1143
+ def create_property_graph(name, opts=OPTS, &block)
1144
+ execute_ddl(create_property_graph_sql(name, PropertyGraph::Generator::Create.new(&block), opts))
1145
+ end
1146
+
470
1147
  # Create a schema in the database. Arguments:
471
1148
  # name :: Name of the schema (e.g. admin)
472
1149
  # opts :: options hash:
@@ -564,6 +1241,15 @@ module Sequel
564
1241
  self << drop_language_sql(name, opts).freeze
565
1242
  end
566
1243
 
1244
+ # Drops a property graph from the database. Arguments:
1245
+ # name :: name of the property graph to drop
1246
+ # opts :: options hash:
1247
+ # :cascade :: Drop other objects depending on this property_graph.
1248
+ # :if_exists :: Don't raise an error if the property graph doesn't exist.
1249
+ def drop_property_graph(name, opts=OPTS)
1250
+ self << drop_property_graph_sql(name, opts).freeze
1251
+ end
1252
+
567
1253
  # Drops a schema from the database. Arguments:
568
1254
  # name :: name of the schema to drop
569
1255
  # opts :: options hash:
@@ -651,6 +1337,82 @@ module Sequel
651
1337
  super
652
1338
  end
653
1339
 
1340
+ # Return a PropertyGraph::Table instance for a property graph search
1341
+ # (a GRAPH_TABLE clause for a SELECT query). Supported on PostgreSQL 19+.
1342
+ #
1343
+ # Arguments:
1344
+ # +property_graph_name+ :: The property graph to query
1345
+ # +initial_vertex_label+ :: The label restriction for the initial vertex for the
1346
+ # graph pattern (can be nil for no label, or an array
1347
+ # or set for restricting to one of multiple labels).
1348
+ # +initial_vertex_opts+ :: The options for the initial vertex, see
1349
+ # PropertyGraph::Table#link for available options.
1350
+ #
1351
+ # The returned instance should be further modified by calling methods on it,
1352
+ # using a similar approach to how datasets work, where the methods return a
1353
+ # modified copy of the receiver. The available methods:
1354
+ #
1355
+ # link :: Add a bidirectional link to a new element (vertex or edge)
1356
+ # to :: Add a directional link from the last element to the new element
1357
+ # from :: Add a direction link from the new element to last element
1358
+ # columns :: Replace the columns the graph table returns
1359
+ # add_columns :: Append to the columns the graph table returns.
1360
+ #
1361
+ # See PropertyGraph::Table for the details of these methods and the arguments
1362
+ # and options they support. Note that for a graph table to be usable in a query,
1363
+ # it must return at least one column, and the last element in the graph pattern
1364
+ # must be a vertex.
1365
+ #
1366
+ # gt = DB.graph_table(:pgn, :iv)
1367
+ # # Not yet usable, does not return any columns
1368
+ #
1369
+ # # Set columns for graph table
1370
+ # gt = gt.columns(:c, Sequel[1].as(:d))
1371
+ # # GRAPH_TABLE ("pgn" MATCH (IS "iv") COLUMNS ("c", 1 AS "d"))
1372
+ #
1373
+ # # Adds directional link to edge, since last (initial) element was a vertex
1374
+ # gt = gt.link(:e1)
1375
+ # # GRAPH_TABLE ("pgn" MATCH (IS "iv")-[IS "e1"] COLUMNS ("c", 1 AS "d"))
1376
+ #
1377
+ # # Adds directional link from edge to vertex, since last element was an edge
1378
+ # gt = gt.to(:v2)
1379
+ # # GRAPH_TABLE ("pgn" MATCH (IS "iv")-[IS "e1"]->(IS "v2") COLUMNS ("c", 1 AS "d"))
1380
+ #
1381
+ # # Adds bidirectional link from vertex to vertex (overriding the default)
1382
+ # gt = gt.link(:v3, vertex: true)
1383
+ # # GRAPH_TABLE ("pgn" MATCH (IS "iv")-[IS "e1"]->(IS "v2")-(IS "v3") COLUMNS ("c", 1 AS "d"))
1384
+ #
1385
+ # # Adds directional link from new edge to last vertex, since last element was an vertex.
1386
+ # # Sets graph pattern variable name and uses it in a WHERE clause for the added element.
1387
+ # gt = gt.from(:e2, var: :a2, where: {Sequel[:a2][:c] => 1})
1388
+ # # GRAPH_TABLE ("pgn" MATCH (IS "iv")-[IS "e1"]->(IS "v2")-(IS "v3")
1389
+ # # <-["a2" IS "e2" WHERE ("a2"."c" = 1)] COLUMNS ("c", 1 AS "d"))
1390
+ #
1391
+ # # Can use nil as a label for no label restriction, both with and without a variable name
1392
+ # gt = gt.to(nil).to(nil, var: :a3)
1393
+ # # GRAPH_TABLE ("pgn" MATCH (IS "iv")-[IS "e1"]->(IS "v2")-(IS "v3")
1394
+ # # <-["a2" IS "e2" WHERE ("a2"."c" = 1)]->[]->("a3") COLUMNS ("c", 1 AS "d"))
1395
+ #
1396
+ # # Can restrict to a one of a set of labels
1397
+ # gt = gt.from([:x, :y], var: :a6)
1398
+ # # GRAPH_TABLE ("pgn" MATCH (IS "iv")-[IS "e1"]->(IS "v2")-(IS "v3")
1399
+ # # <-["a2" IS "e2" WHERE ("a2"."c" = 1)]->[]->("a3")->["a6" IS "x"|"y"] COLUMNS ("c", 1 AS "d"))
1400
+ #
1401
+ # # Add column(s) to the graph table
1402
+ # gt = gt.add_columns(:y)
1403
+ # # GRAPH_TABLE ("pgn" MATCH (IS "iv")-[IS "e1"]->(IS "v2")-(IS "v3")
1404
+ # # <-["a2" IS "e2" WHERE ("a2"."c" = 1)]->[]->("a3")->["a6" IS "x"|"y"]
1405
+ # # COLUMNS ("c", 1 AS "d", "y"))
1406
+ #
1407
+ # DB.from(gt)
1408
+ # # SELECT * FROM GRAPH_TABLE (...)
1409
+ #
1410
+ # DB.from(:x).cross_join(gt)
1411
+ # # SELECT * FROM "x" CROSS JOIN GRAPH_TABLE (...)
1412
+ def graph_table(property_graph_name, initial_vertex_label, initial_vertex_opts=OPTS)
1413
+ PropertyGraph::Table.create(property_graph_name, initial_vertex_label, initial_vertex_opts)
1414
+ end
1415
+
654
1416
  # Immediately apply deferrable constraints.
655
1417
  #
656
1418
  # :constraints :: An identifier of the constraint, or an array of
@@ -741,6 +1503,28 @@ module Sequel
741
1503
  Sequel.synchronize{@primary_key_sequences[quoted_table] = value} if value
742
1504
  end
743
1505
 
1506
+ # Array of symbols specifying property graphs in the current database.
1507
+ # The dataset used is yielded to the block if one is provided,
1508
+ # otherwise, an array of symbols of property graph names is returned.
1509
+ # Supported on PostgreSQL 19+, will be an empty array on lower versions.
1510
+ #
1511
+ # Options:
1512
+ # :qualify :: Return the property graph names as Sequel::SQL::QualifiedIdentifier
1513
+ # instances, using the schema the property graph is located in as the qualifier.
1514
+ # :schema :: The schema to search
1515
+ # :server :: The server to use
1516
+ def property_graphs(opts=OPTS, &block)
1517
+ pg_class_relname('g', opts, &block)
1518
+ end
1519
+
1520
+ # Rename a property graph.
1521
+ #
1522
+ # DB.rename_property_graph(:x, :y)
1523
+ # # ALTER PROPERTY GRAPH x RENAME TO y
1524
+ def rename_property_graph(old_name, new_name)
1525
+ execute_ddl("ALTER PROPERTY GRAPH #{literal(old_name)} RENAME TO #{literal(new_name)}".freeze)
1526
+ end
1527
+
744
1528
  # Rename a schema in the database. Arguments:
745
1529
  # name :: Current name of the schema
746
1530
  # opts :: New name for the schema
@@ -772,12 +1556,12 @@ module Sequel
772
1556
  seq_ds = metadata_dataset.from(:pg_sequence).where(:seqrelid=>regclass_oid(LiteralString.new(seq.freeze)))
773
1557
  increment_by = :seqincrement
774
1558
  min_value = :seqmin
775
- # :nocov:
1559
+ # simplecov:disable
776
1560
  else
777
1561
  seq_ds = metadata_dataset.from(LiteralString.new(seq))
778
1562
  increment_by = :increment_by
779
1563
  min_value = :min_value
780
- # :nocov:
1564
+ # simplecov:enable
781
1565
  end
782
1566
 
783
1567
  get{setval(seq, db[table].select(coalesce(max(pk)+seq_ds.select(increment_by), seq_ds.select(min_value))), false)}
@@ -790,9 +1574,9 @@ module Sequel
790
1574
  # PostgreSQL uses SERIAL psuedo-type instead of AUTOINCREMENT for
791
1575
  # managing incrementing primary keys.
792
1576
  def serial_primary_key_options
793
- # :nocov:
1577
+ # simplecov:disable
794
1578
  auto_increment_key = server_version >= 100002 ? :identity : :serial
795
- # :nocov:
1579
+ # simplecov:enable
796
1580
  {:primary_key => true, auto_increment_key => true, :type=>Integer}
797
1581
  end
798
1582
 
@@ -804,6 +1588,16 @@ module Sequel
804
1588
  @server_version = swallow_database_error{ds.with_sql("SELECT CAST(current_setting('server_version_num') AS integer) AS v").single_value} || 0
805
1589
  end
806
1590
 
1591
+ # Change the schema for a property graph. Options:
1592
+ # :if_exists :: Use the IF EXISTS clause to not raise an error if the
1593
+ # property graph does not exist.
1594
+ #
1595
+ # DB.set_property_graph_schema(:x, :y)
1596
+ # # ALTER PROPERTY GRAPH x SET SCHEMA y
1597
+ def set_property_graph_schema(old_name, new_name, opts=OPTS)
1598
+ execute_ddl("ALTER PROPERTY GRAPH#{" IF EXISTS" if opts[:if_exists]} #{literal(old_name)} SET SCHEMA #{literal(new_name)}".freeze)
1599
+ end
1600
+
807
1601
  # PostgreSQL supports CREATE TABLE IF NOT EXISTS on 9.1+
808
1602
  def supports_create_table_if_not_exists?
809
1603
  server_version >= 90100
@@ -979,12 +1773,12 @@ module Sequel
979
1773
  if server_version >= 90500
980
1774
  cpos = Sequel.expr{array_position(co[:conkey], ctable[:attnum])}
981
1775
  rpos = Sequel.expr{array_position(co[:confkey], rtable[:attnum])}
982
- # :nocov:
1776
+ # simplecov:disable
983
1777
  else
984
1778
  range = 0...32
985
1779
  cpos = Sequel.expr{SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(co[:conkey], [x]), x]}, 32, ctable[:attnum])}
986
1780
  rpos = Sequel.expr{SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(co[:confkey], [x]), x]}, 32, rtable[:attnum])}
987
- # :nocov:
1781
+ # simplecov:enable
988
1782
  end
989
1783
 
990
1784
  ds = metadata_dataset.
@@ -1021,19 +1815,19 @@ module Sequel
1021
1815
  def _add_validated_enforced_constraint_columns(ds)
1022
1816
  validated_cond = if server_version >= 90100
1023
1817
  Sequel[:convalidated]
1024
- # :nocov:
1818
+ # simplecov:disable
1025
1819
  else
1026
1820
  Sequel.cast(true, TrueClass)
1027
- # :nocov:
1821
+ # simplecov:enable
1028
1822
  end
1029
1823
  ds = ds.select_append(validated_cond.as(:validated))
1030
1824
 
1031
1825
  enforced_cond = if server_version >= 180000
1032
1826
  Sequel[:conenforced]
1033
- # :nocov:
1827
+ # simplecov:disable
1034
1828
  else
1035
1829
  Sequel.cast(true, TrueClass)
1036
- # :nocov:
1830
+ # simplecov:enable
1037
1831
  end
1038
1832
  ds = ds.select_append(enforced_cond.as(:enforced))
1039
1833
 
@@ -1045,11 +1839,11 @@ module Sequel
1045
1839
  @_indexes_ds ||= begin
1046
1840
  if server_version >= 90500
1047
1841
  order = [Sequel[:indc][:relname], Sequel.function(:array_position, Sequel[:ind][:indkey], Sequel[:att][:attnum])]
1048
- # :nocov:
1842
+ # simplecov:disable
1049
1843
  else
1050
1844
  range = 0...32
1051
1845
  order = [Sequel[:indc][:relname], SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(Sequel[:ind][:indkey], [x]), x]}, 32, Sequel[:att][:attnum])]
1052
- # :nocov:
1846
+ # simplecov:enable
1053
1847
  end
1054
1848
 
1055
1849
  attnums = SQL::Function.new(:ANY, Sequel[:ind][:indkey])
@@ -1067,10 +1861,10 @@ module Sequel
1067
1861
  order(*order).
1068
1862
  select{[indc[:relname].as(:name), ind[:indisunique].as(:unique), att[:attname].as(:column), con[:condeferrable].as(:deferrable)]}
1069
1863
 
1070
- # :nocov:
1864
+ # simplecov:disable
1071
1865
  ds = ds.where(:indisready=>true) if server_version >= 80300
1072
1866
  ds = ds.where(:indislive=>true) if server_version >= 90300
1073
- # :nocov:
1867
+ # simplecov:enable
1074
1868
 
1075
1869
  ds
1076
1870
  end
@@ -1163,14 +1957,14 @@ module Sequel
1163
1957
  where{pg_attribute[:attnum] > 0}.
1164
1958
  order{pg_attribute[:attnum]}
1165
1959
 
1166
- # :nocov:
1960
+ # simplecov:disable
1167
1961
  if server_version > 100000
1168
- # :nocov:
1962
+ # simplecov:enable
1169
1963
  ds = ds.select_append{pg_attribute[:attidentity]}
1170
1964
 
1171
- # :nocov:
1965
+ # simplecov:disable
1172
1966
  if server_version > 120000
1173
- # :nocov:
1967
+ # simplecov:enable
1174
1968
  ds = ds.select_append{Sequel.~(pg_attribute[:attgenerated]=>'').as(:generated)}
1175
1969
  end
1176
1970
  end
@@ -1204,6 +1998,62 @@ module Sequel
1204
1998
  raise e unless /canceling statement due to (?:statement|lock) timeout/ =~ e.message
1205
1999
  end
1206
2000
 
2001
+ # SQL statement for a single ALTER PROPERTY GRAPH operation.
2002
+ def alter_property_graph_op_sql(name, op)
2003
+ sql = String.new << "ALTER PROPERTY GRAPH " << quote_schema_table(name) << " "
2004
+
2005
+ case op_type = op[:op]
2006
+ when :add_vertex_tables
2007
+ sql << "ADD VERTEX TABLES (" <<
2008
+ op[:tables].map do |vertex|
2009
+ create_property_graph_table_sql(vertex) <<
2010
+ create_property_graph_labels_sql(vertex.labels)
2011
+ end.join(', ') << ")"
2012
+ when :add_edge_tables
2013
+ sql << "ADD EDGE TABLES (" <<
2014
+ op[:tables].map do |edge|
2015
+ create_property_graph_table_sql(edge) <<
2016
+ " SOURCE " << create_property_graph_edge_side_sql(edge.source) <<
2017
+ " DESTINATION " << create_property_graph_edge_side_sql(edge.destination) <<
2018
+ create_property_graph_labels_sql(edge.labels)
2019
+ end.join(', ') << ")"
2020
+ when :drop_vertex_tables, :drop_edge_tables
2021
+ sql << (op_type == :drop_vertex_tables ? "DROP VERTEX TABLES " : "DROP EDGE TABLES ") <<
2022
+ literal(op[:aliases])
2023
+ when :add_label
2024
+ sql << alter_property_graph_element_table_sql(op)
2025
+ op[:labels].each do |label_name, properties|
2026
+ sql << " ADD LABEL " << quote_identifier(label_name) <<
2027
+ create_property_graph_properties_clause_sql(properties)
2028
+ end
2029
+ when :drop_label
2030
+ sql << alter_property_graph_element_table_sql(op) <<
2031
+ " DROP LABEL " << quote_identifier(op[:label])
2032
+ when :add_properties
2033
+ sql << alter_property_graph_element_table_sql(op) <<
2034
+ " ALTER LABEL " << quote_identifier(op[:label]) << " ADD PROPERTIES " <<
2035
+ literal(op[:properties])
2036
+ when :drop_properties
2037
+ sql << alter_property_graph_element_table_sql(op) <<
2038
+ " ALTER LABEL " << quote_identifier(op[:label]) << " DROP PROPERTIES " <<
2039
+ literal(op[:properties])
2040
+ else # when :set_owner
2041
+ sql << "OWNER TO " << literal(op[:owner])
2042
+ end
2043
+
2044
+ case op_type
2045
+ when :drop_vertex_tables, :drop_edge_tables, :drop_label, :drop_properties
2046
+ sql << " CASCADE" if op[:cascade]
2047
+ end
2048
+
2049
+ sql
2050
+ end
2051
+
2052
+ # SQL fragment for the ALTER PROPERTY GRAPH ALTER {VERTEX|EDGE} TABLE prefix
2053
+ def alter_property_graph_element_table_sql(op)
2054
+ "ALTER #{op[:kind] == :vertex ? 'VERTEX' : 'EDGE'} TABLE #{quote_identifier(op[:name])}"
2055
+ end
2056
+
1207
2057
  def alter_table_add_column_sql(table, op)
1208
2058
  "ADD COLUMN#{' IF NOT EXISTS' if op[:if_not_exists]} #{column_definition_sql(op)}"
1209
2059
  end
@@ -1597,6 +2447,87 @@ module Sequel
1597
2447
  sql
1598
2448
  end
1599
2449
 
2450
+ # SQL statement for creating a property graph.
2451
+ def create_property_graph_sql(name, data, opts=OPTS)
2452
+ sql = String.new
2453
+ sql << "CREATE "
2454
+ sql << "TEMPORARY " if opts[:temp]
2455
+ sql << "PROPERTY GRAPH "
2456
+ sql << quote_schema_table(name)
2457
+
2458
+ unless data.vertices.empty?
2459
+ sql << " VERTEX TABLES ("
2460
+ sql << data.vertices.map do |vertex|
2461
+ create_property_graph_table_sql(vertex) <<
2462
+ create_property_graph_labels_sql(vertex.labels)
2463
+ end.join(', ')
2464
+ sql << ")"
2465
+ end
2466
+
2467
+ unless data.edges.empty?
2468
+ sql << " EDGE TABLES ("
2469
+ sql << data.edges.map do |edge|
2470
+ create_property_graph_table_sql(edge) <<
2471
+ " SOURCE " << create_property_graph_edge_side_sql(edge.source) <<
2472
+ " DESTINATION " << create_property_graph_edge_side_sql(edge.destination) <<
2473
+ create_property_graph_labels_sql(edge.labels)
2474
+ end.join(', ')
2475
+ sql << ")"
2476
+ end
2477
+
2478
+ sql
2479
+ end
2480
+
2481
+ # SQL fragment for the SOURCE or DESTINATION clause of an edge in a property graph.
2482
+ def create_property_graph_edge_side_sql(side)
2483
+ sql = String.new
2484
+ if side.key
2485
+ sql << "KEY " << literal(side.key) << " REFERENCES "
2486
+ end
2487
+ sql << quote_identifier(side.name)
2488
+ if side.references
2489
+ sql << " " << literal(side.references)
2490
+ end
2491
+ sql
2492
+ end
2493
+
2494
+ # SQL fragment for the table name and KEY clause used for vertices and edges in
2495
+ # a property graph.
2496
+ def create_property_graph_table_sql(element)
2497
+ sql = String.new
2498
+ sql << literal(element.name)
2499
+
2500
+ if key = element.key
2501
+ sql << " KEY " << literal(key)
2502
+ end
2503
+
2504
+ sql
2505
+ end
2506
+
2507
+ # SQL fragment for the LABEL/PROPERTIES clauses used for vertices and
2508
+ # edges in a property graph.
2509
+ def create_property_graph_labels_sql(labels)
2510
+ labels.map do |name, properties|
2511
+ sql = String.new
2512
+ sql << " LABEL " << quote_identifier(name) if name
2513
+ sql << create_property_graph_properties_clause_sql(properties)
2514
+ sql
2515
+ end.join
2516
+ end
2517
+
2518
+ # SQL fragment for the NO PROPERTIES, PROPERTIES ALL COLUMNS, or
2519
+ # PROPERTIES (...) clause for a property graph element or label.
2520
+ def create_property_graph_properties_clause_sql(properties)
2521
+ case properties
2522
+ when nil, :all
2523
+ " PROPERTIES ALL COLUMNS"
2524
+ when false, :none, [].freeze
2525
+ " NO PROPERTIES"
2526
+ else
2527
+ " PROPERTIES #{literal(properties)}"
2528
+ end
2529
+ end
2530
+
1600
2531
  # SQL for creating a schema.
1601
2532
  def create_schema_sql(name, opts=OPTS)
1602
2533
  "CREATE SCHEMA #{'IF NOT EXISTS ' if opts[:if_not_exists]}#{quote_identifier(name)}#{" AUTHORIZATION #{literal(opts[:owner])}" if opts[:owner]}"
@@ -1623,7 +2554,7 @@ module Sequel
1623
2554
  "#{super}#{create_table_suffix_sql(name, options)}"
1624
2555
  end
1625
2556
 
1626
- # Handle various PostgreSQl specific table extensions such as inheritance,
2557
+ # Handle various PostgreSQL specific table extensions such as inheritance,
1627
2558
  # partitioning, tablespaces, and foreign tables.
1628
2559
  def create_table_suffix_sql(name, options)
1629
2560
  sql = String.new
@@ -1710,6 +2641,11 @@ module Sequel
1710
2641
  "DROP LANGUAGE#{' IF EXISTS' if opts[:if_exists]} #{name}#{' CASCADE' if opts[:cascade]}"
1711
2642
  end
1712
2643
 
2644
+ # SQL for dropping a property graph from the database.
2645
+ def drop_property_graph_sql(name, opts=OPTS)
2646
+ "DROP PROPERTY GRAPH#{' IF EXISTS' if opts[:if_exists]} #{literal(name)}#{' CASCADE' if opts[:cascade]}"
2647
+ end
2648
+
1713
2649
  # SQL for dropping a schema from the database.
1714
2650
  def drop_schema_sql(name, opts=OPTS)
1715
2651
  "DROP SCHEMA#{' IF EXISTS' if opts[:if_exists]} #{quote_identifier(name)}#{' CASCADE' if opts[:cascade]}"
@@ -1922,9 +2858,9 @@ module Sequel
1922
2858
  row[:auto_increment] = !!(row[:default] =~ /\A(?:nextval)/i) || identity == 'a' || identity == 'd'
1923
2859
  end
1924
2860
 
1925
- # :nocov:
2861
+ # simplecov:disable
1926
2862
  if server_version >= 90600
1927
- # :nocov:
2863
+ # simplecov:enable
1928
2864
  case row[:oid]
1929
2865
  when 1082
1930
2866
  row[:min_value] = MIN_DATE
@@ -2013,9 +2949,9 @@ module Sequel
2013
2949
 
2014
2950
  # PostgreSQL 9.4+ supports views with check option.
2015
2951
  def view_with_check_option_support
2016
- # :nocov:
2952
+ # simplecov:disable
2017
2953
  :local if server_version >= 90400
2018
- # :nocov:
2954
+ # simplecov:enable
2019
2955
  end
2020
2956
  end
2021
2957
 
@@ -2118,12 +3054,12 @@ module Sequel
2118
3054
  rows[0]
2119
3055
  elsif rows.all?{|row| String === row}
2120
3056
  rows.join("\r\n")
2121
- # :nocov:
3057
+ # simplecov:disable
2122
3058
  else
2123
3059
  # This branch is unreachable in tests, but it seems better to just return
2124
3060
  # all rows than throw in error if this case actually happens.
2125
3061
  rows
2126
- # :nocov:
3062
+ # simplecov:enable
2127
3063
  end
2128
3064
  end
2129
3065
 
@@ -2245,8 +3181,11 @@ module Sequel
2245
3181
  # Handle uniqueness violations when inserting, by updating the conflicting row, using
2246
3182
  # ON CONFLICT. With no options, uses ON CONFLICT DO NOTHING. Options:
2247
3183
  # :conflict_where :: The index filter, when using a partial index to determine uniqueness.
2248
- # :constraint :: An explicit constraint name, has precendence over :target.
3184
+ # :constraint :: An explicit constraint name, has precedence over :target.
2249
3185
  # :target :: The column name or expression to handle uniqueness violations on.
3186
+ # :select :: Use ON CONFLICT DO SELECT. This will use the dataset's lock style.
3187
+ # You must use #returning on this dataset when using this option.
3188
+ # :select_where :: A WHERE condition to use for the select.
2250
3189
  # :update :: A hash of columns and values to set. Uses ON CONFLICT DO UPDATE.
2251
3190
  # :update_where :: A WHERE condition to use for the update.
2252
3191
  #
@@ -2277,7 +3216,21 @@ module Sequel
2277
3216
  # # INSERT INTO TABLE (a, b) VALUES (1, 2)
2278
3217
  # # ON CONFLICT ON CONSTRAINT table_a_uidx
2279
3218
  # # DO UPDATE SET b = excluded.b WHERE (table.status_id = 1)
3219
+ #
3220
+ # DB[:table].returning(:a).insert_conflict(target: :a, select: true).insert(a: 1, b: 2)
3221
+ # # INSERT INTO TABLE (a, b) VALUES (1, 2)
3222
+ # # ON CONFLICT (a) DO SELECT RETURNING a
3223
+ #
3224
+ # DB[:table].returning(:a).for_update.
3225
+ # insert_conflict(target: :a, select: true, select_where: {Sequel[:excluded][:b] => 3}).
3226
+ # insert(a: 1, b: 2)
3227
+ # # INSERT INTO TABLE (a, b) VALUES (1, 2)
3228
+ # # ON CONFLICT (a) DO SELECT FOR UPDATE WHERE (excluded.b = 3) RETURNING a
2280
3229
  def insert_conflict(opts=OPTS)
3230
+ if opts[:select] && opts[:update]
3231
+ raise Error, "Cannot provide both :select and :update options to insert_conflict"
3232
+ end
3233
+
2281
3234
  clone(:insert_conflict => opts)
2282
3235
  end
2283
3236
 
@@ -2518,14 +3471,14 @@ module Sequel
2518
3471
  server_version >= 90500
2519
3472
  end
2520
3473
 
2521
- # :nocov:
3474
+ # simplecov:disable
2522
3475
 
2523
3476
  # PostgreSQL supports timezones in literal timestamps
2524
3477
  def supports_timestamp_timezones?
2525
3478
  # SEQUEL6: Remove
2526
3479
  true
2527
3480
  end
2528
- # :nocov:
3481
+ # simplecov:enable
2529
3482
 
2530
3483
  # PostgreSQL 8.4+ supports WINDOW clause.
2531
3484
  def supports_window_clause?
@@ -2640,7 +3593,7 @@ module Sequel
2640
3593
  end
2641
3594
 
2642
3595
  # Use from_self for aggregate dataset using VALUES.
2643
- def aggreate_dataset_use_from_self?
3596
+ def aggregate_dataset_use_from_self?
2644
3597
  super || @opts[:values]
2645
3598
  end
2646
3599
 
@@ -2702,7 +3655,7 @@ module Sequel
2702
3655
  origin = String.new
2703
3656
  origin << 'EXPLAIN '
2704
3657
 
2705
- # :nocov:
3658
+ # simplecov:disable
2706
3659
  if server_version < 90000
2707
3660
  if opts[:analyze]
2708
3661
  origin << 'ANALYZE '
@@ -2710,7 +3663,7 @@ module Sequel
2710
3663
 
2711
3664
  return origin
2712
3665
  end
2713
- # :nocov:
3666
+ # simplecov:enable
2714
3667
 
2715
3668
  comma = nil
2716
3669
  paren = "("
@@ -2790,13 +3743,19 @@ module Sequel
2790
3743
  if values = opts[:update]
2791
3744
  sql << " DO UPDATE SET "
2792
3745
  update_sql_values_hash(sql, values)
2793
- if update_where = opts[:update_where]
2794
- sql << " WHERE "
2795
- literal_append(sql, update_where)
2796
- end
3746
+ where = opts[:update_where]
3747
+ elsif opts[:select]
3748
+ sql << " DO SELECT"
3749
+ select_lock_sql(sql)
3750
+ where = opts[:select_where]
2797
3751
  else
2798
3752
  sql << " DO NOTHING"
2799
3753
  end
3754
+
3755
+ if where
3756
+ sql << " WHERE "
3757
+ literal_append(sql, where)
3758
+ end
2800
3759
  end
2801
3760
  end
2802
3761