brick 1.0.199 → 1.0.200
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/lib/brick/extensions.rb +112 -602
- data/lib/brick/frameworks/rails/engine.rb +1 -29
- data/lib/brick/frameworks/rails/form_tags.rb +1 -1
- data/lib/brick/reflect_tables.rb +582 -0
- data/lib/brick/route_mapper.rb +20 -4
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +1 -0
- metadata +3 -2
data/lib/brick/extensions.rb
CHANGED
@@ -436,7 +436,11 @@ module ActiveRecord
|
|
436
436
|
order_by = ordering&.each_with_object([]) do |ord_part, s| # %%% If a term is also used as an eqi-condition in the WHERE clause, it can be omitted from ORDER BY
|
437
437
|
case ord_part
|
438
438
|
when String
|
439
|
-
ord_expr =
|
439
|
+
ord_expr = if ord_part.index('(') # Any kind of SQL function at play here?
|
440
|
+
ord_part.gsub('^^^', _br_quoted_name(table_name))
|
441
|
+
else
|
442
|
+
_br_quoted_name(ord_part.gsub('^^^', table_name))
|
443
|
+
end
|
440
444
|
s << Arel.sql(ord_expr)
|
441
445
|
order_by_txt&.<<(ord_expr.index('.') ? "Arel.sql(#{ord_expr.inspect})" : ord_part.inspect)
|
442
446
|
else # Expecting only Symbol
|
@@ -887,12 +891,16 @@ module ActiveRecord
|
|
887
891
|
|
888
892
|
tbl_alias = unique63("b_r_#{hm.name}", previous)
|
889
893
|
on_clause = []
|
890
|
-
hm_selects = if
|
891
|
-
fk_col.
|
892
|
-
|
893
|
-
|
894
|
+
hm_selects = if !pri_key.is_a?(Array) # Probable standard key?
|
895
|
+
if fk_col.is_a?(Array) # Foreign is composite but not Primary? OK, or choose the first part of the foreign key if nothing else
|
896
|
+
fk_col = fk_col.find { |col_name| col_name == pri_key } || # Try to associate with the same-named part of the foreign key ...
|
897
|
+
fk_col.first # ... and if no good match, just choose the first part
|
898
|
+
end
|
894
899
|
on_clause << "#{_br_quoted_name("#{tbl_alias}.#{fk_col}")} = #{_br_quoted_name("#{pri_tbl.table_name}.#{pri_key}")}"
|
895
900
|
[fk_col]
|
901
|
+
else # Composite key
|
902
|
+
fk_col.each_with_index { |fk_col_part, idx| on_clause << "#{tbl_alias}.#{fk_col_part} = #{pri_tbl.table_name}.#{pri_key[idx]}" }
|
903
|
+
fk_col.dup
|
896
904
|
end
|
897
905
|
if poly_type
|
898
906
|
hm_selects << poly_type
|
@@ -1166,6 +1174,34 @@ Might want to add this in your brick.rb:
|
|
1166
1174
|
end
|
1167
1175
|
end
|
1168
1176
|
|
1177
|
+
if ::Brick.enable_routes? && Object.const_defined?('ActionDispatch')
|
1178
|
+
require 'brick/route_mapper'
|
1179
|
+
ActionDispatch::Routing::RouteSet.class_exec do
|
1180
|
+
# In order to defer auto-creation of any routes that already exist, calculate Brick routes only after having loaded all others
|
1181
|
+
prepend ::Brick::RouteSet
|
1182
|
+
end
|
1183
|
+
ActionDispatch::Routing::Mapper.class_exec do
|
1184
|
+
include ::Brick::RouteMapper
|
1185
|
+
end
|
1186
|
+
|
1187
|
+
# Do the root route before the Rails Welcome one would otherwise take precedence
|
1188
|
+
if (route = ::Brick.config.default_route_fallback).present?
|
1189
|
+
action = "#{route}#{'#index' unless route.index('#')}"
|
1190
|
+
if ::Brick.config.path_prefix
|
1191
|
+
::Rails.application.routes.append do
|
1192
|
+
send(:namespace, ::Brick.config.path_prefix) do
|
1193
|
+
send(:root, action)
|
1194
|
+
end
|
1195
|
+
end
|
1196
|
+
elsif ::Rails.application.routes.named_routes.send(:routes)[:root].nil?
|
1197
|
+
::Rails.application.routes.append do
|
1198
|
+
send(:root, action)
|
1199
|
+
end
|
1200
|
+
end
|
1201
|
+
::Brick.established_drf = "/#{::Brick.config.path_prefix}#{action[action.index('#')..-1]}"
|
1202
|
+
end
|
1203
|
+
end
|
1204
|
+
|
1169
1205
|
if Object.const_defined?('ActionView')
|
1170
1206
|
require 'brick/frameworks/rails/form_tags'
|
1171
1207
|
require 'brick/frameworks/rails/form_builder'
|
@@ -1298,19 +1334,23 @@ end
|
|
1298
1334
|
unless (is_tnp_module = (is_brick_prefix && !is_controller && ::Brick.config.table_name_prefixes.values.include?(requested)))
|
1299
1335
|
# ... first look around for an existing module or class.
|
1300
1336
|
desired_classname = (self == Object || !name) ? requested : "#{name}::#{requested}"
|
1301
|
-
if (
|
1337
|
+
if (self.const_defined?(args.first) && (possible = self.const_get(args.first)) &&
|
1302
1338
|
# Reset `possible` if it's a controller request that's not a perfect match
|
1303
1339
|
# Was: (possible = nil) but changed to #local_variable_set in order to suppress the "= should be ==" warning
|
1304
1340
|
(possible&.name == desired_classname || (is_controller && binding.local_variable_set(:possible, nil)))) ||
|
1341
|
+
|
1305
1342
|
# Try to require the respective Ruby file
|
1343
|
+
# ((filename = ActiveSupport::Dependencies.search_for_file(desired_classname.underscore)) &&
|
1344
|
+
# (require_dependency(filename) || true) &&
|
1306
1345
|
((filename = ActiveSupport::Dependencies.search_for_file(desired_classname.underscore) ||
|
1307
1346
|
(self != Object && ActiveSupport::Dependencies.search_for_file((desired_classname = requested).underscore))
|
1308
1347
|
) && (require_dependency(filename) || true) &&
|
1309
1348
|
((possible = self.const_get(args.first)) && possible.name == desired_classname)
|
1310
1349
|
) ||
|
1350
|
+
|
1311
1351
|
# If any class has turned up so far (and we're not in the middle of eager loading)
|
1312
1352
|
# then return what we've found.
|
1313
|
-
(
|
1353
|
+
(possible&.module_parent == base_module && !::Brick.is_eager_loading) # Used to also have: && possible != self
|
1314
1354
|
if ((!brick_root && (filename || possible.instance_of?(Class))) ||
|
1315
1355
|
(possible.instance_of?(Module) && possible&.module_parent == self) ||
|
1316
1356
|
(possible.instance_of?(Class) && possible == self)) && # Are we simply searching for ourselves?
|
@@ -1342,13 +1382,14 @@ end
|
|
1342
1382
|
singular_class_name = ::Brick.namify(plural_class_name, :underscore).singularize.camelize
|
1343
1383
|
full_class_name << "::#{singular_class_name}"
|
1344
1384
|
skip_controller = nil
|
1345
|
-
|
1346
|
-
|
1347
|
-
(
|
1348
|
-
|
1349
|
-
|
1350
|
-
|
1351
|
-
|
1385
|
+
begin
|
1386
|
+
if plural_class_name == 'BrickOpenapi' ||
|
1387
|
+
(
|
1388
|
+
(::Brick.config.add_status || ::Brick.config.add_orphans) &&
|
1389
|
+
plural_class_name == 'BrickGem'
|
1390
|
+
# Was: ) || (model = self.const_get(full_class_name))
|
1391
|
+
) || (model = Object.const_get(full_class_name))
|
1392
|
+
# puts "#{self.name} - #{full_class_name}"
|
1352
1393
|
|
1353
1394
|
# In the very rare case that we've picked up a MODULE which has the same name as what would be the
|
1354
1395
|
# resource's MODEL name, just build out an appropriate auto-model on-the-fly. (RailsDevs code has this in PayCustomer.)
|
@@ -1356,10 +1397,10 @@ end
|
|
1356
1397
|
if model && !model.is_a?(Class)
|
1357
1398
|
model, _code = Object.send(:build_model, relations, model.module_parent, model.module_parent.name, singular_class_name)
|
1358
1399
|
end
|
1359
|
-
rescue NameError # If the const_get for the model has failed...
|
1360
|
-
skip_controller = true
|
1361
|
-
# ... then just fall through and allow it to fail when trying to load the ____Controller class normally.
|
1362
1400
|
end
|
1401
|
+
rescue NameError # If the const_get for the model has failed...
|
1402
|
+
skip_controller = true
|
1403
|
+
# ... then just fall through and allow it to fail when trying to load the ____Controller class normally.
|
1363
1404
|
end
|
1364
1405
|
unless skip_controller
|
1365
1406
|
Object.send(:build_controller, self, class_name, plural_class_name, model, relations)
|
@@ -1452,7 +1493,7 @@ end
|
|
1452
1493
|
base_module._brick_const_missing(*args)
|
1453
1494
|
# elsif base_module != Object
|
1454
1495
|
# module_parent.const_missing(*args)
|
1455
|
-
elsif Rails.respond_to?(:autoloaders) && # After finding nothing else, if Zeitwerk is enabled ...
|
1496
|
+
elsif Object.const_defined?('Rails') && ::Rails.respond_to?(:autoloaders) && # After finding nothing else, if Zeitwerk is enabled ...
|
1456
1497
|
(Rails::Autoloaders.respond_to?(:zeitwerk_enabled?) ? Rails::Autoloaders.zeitwerk_enabled? : true)
|
1457
1498
|
self._brick_const_missing(*args) # ... rely solely on Zeitwerk.
|
1458
1499
|
else # Classic mode
|
@@ -1477,7 +1518,9 @@ class Object
|
|
1477
1518
|
private
|
1478
1519
|
|
1479
1520
|
def build_model(relations, base_module, base_name, class_name, inheritable_name = nil)
|
1480
|
-
tnp = ::Brick.config.table_name_prefixes&.find { |p| p.last == base_module.name }
|
1521
|
+
tnp = ::Brick.config.table_name_prefixes&.find { |p| p.last == base_module.name }
|
1522
|
+
# return [base_module, ''] if !base_module.is_a?(Class) && base_name == tnp&.last
|
1523
|
+
|
1481
1524
|
if (base_model = (::Brick.config.sti_namespace_prefixes&.fetch("::#{base_module.name}::#{class_name}", nil) || # Are we part of an auto-STI namespace? ...
|
1482
1525
|
::Brick.config.sti_namespace_prefixes&.fetch("::#{base_module.name}::", nil))&.constantize) ||
|
1483
1526
|
base_module != Object # ... or otherwise already in some namespace?
|
@@ -1499,7 +1542,7 @@ class Object
|
|
1499
1542
|
table_name = if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil) || ::Brick.existing_stis[model_name]&.constantize)
|
1500
1543
|
base_model.table_name
|
1501
1544
|
else
|
1502
|
-
"#{tnp}#{ActiveSupport::Inflector.pluralize(singular_table_name)}"
|
1545
|
+
"#{tnp&.first}#{ActiveSupport::Inflector.pluralize(singular_table_name)}"
|
1503
1546
|
end
|
1504
1547
|
if ::Brick.apartment_multitenant &&
|
1505
1548
|
Apartment.excluded_models.include?(table_name.singularize.camelize)
|
@@ -1553,7 +1596,7 @@ class Object
|
|
1553
1596
|
else
|
1554
1597
|
# Class for auto-generated models to inherit from
|
1555
1598
|
base_model = (::Brick.config.models_inherit_from ||= (begin
|
1556
|
-
::ApplicationRecord
|
1599
|
+
Object.const_defined?('ApplicationRecord') ? ::ApplicationRecord : ::ActiveRecord::Base
|
1557
1600
|
rescue StandardError => ex
|
1558
1601
|
::ActiveRecord::Base
|
1559
1602
|
end))
|
@@ -1728,7 +1771,8 @@ class Object
|
|
1728
1771
|
end
|
1729
1772
|
|
1730
1773
|
def build_bt_or_hm(full_name, relations, relation, hmts, assoc, inverse_assoc_name, inverse_table, code)
|
1731
|
-
singular_table_name = inverse_table&.singularize
|
1774
|
+
return unless (singular_table_name = inverse_table&.singularize)
|
1775
|
+
|
1732
1776
|
options = {}
|
1733
1777
|
macro = if assoc[:is_bt]
|
1734
1778
|
# Try to take care of screwy names if this is a belongs_to going to an STI subclass
|
@@ -2592,586 +2636,46 @@ end
|
|
2592
2636
|
# ==========================================================
|
2593
2637
|
# Get info on all relations during first database connection
|
2594
2638
|
# ==========================================================
|
2639
|
+
if Object.const_defined?('OTR') && OTR.const_defined?('ActiveRecord')
|
2640
|
+
OTR::ActiveRecord.class_exec do
|
2641
|
+
class << self
|
2642
|
+
alias _brick_establish_connection! establish_connection!
|
2643
|
+
def establish_connection!(*args)
|
2644
|
+
conn = _brick_establish_connection!(*args)
|
2645
|
+
return conn unless ::Brick.config.mode == :on
|
2595
2646
|
|
2596
|
-
|
2597
|
-
|
2598
|
-
|
2599
|
-
|
2600
|
-
|
2601
|
-
alias _brick_establish_connection establish_connection
|
2602
|
-
def establish_connection(*args)
|
2603
|
-
conn = _brick_establish_connection(*args)
|
2604
|
-
return conn unless ::Brick.config.mode == :on
|
2605
|
-
|
2606
|
-
begin
|
2607
|
-
# Overwrite SQLite's #begin_db_transaction so it opens in IMMEDIATE mode instead of
|
2608
|
-
# the default DEFERRED mode.
|
2609
|
-
# https://discuss.rubyonrails.org/t/failed-write-transaction-upgrades-in-sqlite3/81480/2
|
2610
|
-
if ActiveRecord::Base.connection.adapter_name == 'SQLite'
|
2611
|
-
arca = ::ActiveRecord::ConnectionAdapters
|
2612
|
-
db_statements = arca::SQLite3.const_defined?('DatabaseStatements') ? arca::SQLite3::DatabaseStatements : arca::SQLite3::SchemaStatements
|
2613
|
-
# Rails 7.1 and later
|
2614
|
-
if arca::AbstractAdapter.private_instance_methods.include?(:with_raw_connection)
|
2615
|
-
db_statements.define_method(:begin_db_transaction) do
|
2616
|
-
log("begin immediate transaction", "TRANSACTION") do
|
2617
|
-
with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
|
2618
|
-
conn.transaction(:immediate)
|
2619
|
-
end
|
2620
|
-
end
|
2621
|
-
end
|
2622
|
-
else # Rails < 7.1
|
2623
|
-
db_statements.define_method(:begin_db_transaction) do
|
2624
|
-
log('begin immediate transaction', 'TRANSACTION') { @connection.transaction(:immediate) }
|
2625
|
-
end
|
2647
|
+
begin
|
2648
|
+
# ::Brick.is_db_present = true
|
2649
|
+
::Brick.reflect_tables
|
2650
|
+
rescue ActiveRecord::NoDatabaseError
|
2651
|
+
# ::Brick.is_db_present = false
|
2626
2652
|
end
|
2653
|
+
Module.class_exec &::Brick::ADD_CONST_MISSING
|
2654
|
+
conn
|
2627
2655
|
end
|
2628
|
-
# ::Brick.is_db_present = true
|
2629
|
-
_brick_reflect_tables
|
2630
|
-
rescue ActiveRecord::NoDatabaseError
|
2631
|
-
# ::Brick.is_db_present = false
|
2632
2656
|
end
|
2633
|
-
conn
|
2634
2657
|
end
|
2658
|
+
else
|
2659
|
+
if ActiveRecord.const_defined?('ConnectionHandling')
|
2660
|
+
ActiveRecord::ConnectionHandling
|
2661
|
+
else
|
2662
|
+
ActiveRecord::ConnectionAdapters::ConnectionHandler
|
2663
|
+
end.class_exec do
|
2664
|
+
alias _brick_establish_connection establish_connection
|
2665
|
+
def establish_connection(*args)
|
2666
|
+
conn = _brick_establish_connection(*args)
|
2667
|
+
return conn unless ::Brick.config.mode == :on
|
2635
2668
|
|
2636
|
-
|
2637
|
-
|
2638
|
-
|
2639
|
-
|
2640
|
-
|
2641
|
-
# return if ActiveRecord::Base.connection.current_database == 'postgres'
|
2642
|
-
|
2643
|
-
orig_schema = nil
|
2644
|
-
if (relations = ::Brick.relations).keys == [:db_name]
|
2645
|
-
::Brick.remove_instance_variable(:@_additional_references_loaded) if ::Brick.instance_variable_defined?(:@_additional_references_loaded)
|
2646
|
-
|
2647
|
-
# --------------------------------------------
|
2648
|
-
# 1. Load three initializers early
|
2649
|
-
# (inflectsions.rb, brick.rb, apartment.rb)
|
2650
|
-
# Very first thing, load inflections since we'll be using .pluralize and .singularize on table and model names
|
2651
|
-
if File.exist?(inflections = ::Rails.root&.join('config/initializers/inflections.rb') || '')
|
2652
|
-
load inflections
|
2653
|
-
end
|
2654
|
-
# Now the Brick initializer since there may be important schema things configured
|
2655
|
-
if !::Brick.initializer_loaded && File.exist?(brick_initializer = ::Rails.root&.join('config/initializers/brick.rb') || '')
|
2656
|
-
::Brick.initializer_loaded = load brick_initializer
|
2657
|
-
|
2658
|
-
# After loading the initializer, add compatibility for ActiveStorage and ActionText if those haven't already been
|
2659
|
-
# defined. (Further JSON configuration for ActiveStorage metadata happens later in the after_initialize hook.)
|
2660
|
-
['ActiveStorage', 'ActionText'].each do |ar_extension|
|
2661
|
-
if Object.const_defined?(ar_extension) &&
|
2662
|
-
(extension = Object.const_get(ar_extension)).respond_to?(:table_name_prefix) &&
|
2663
|
-
!::Brick.config.table_name_prefixes.key?(as_tnp = extension.table_name_prefix)
|
2664
|
-
::Brick.config.table_name_prefixes[as_tnp] = ar_extension
|
2665
|
-
end
|
2666
|
-
end
|
2667
|
-
|
2668
|
-
# Support the followability gem: https://github.com/nejdetkadir/followability
|
2669
|
-
if Object.const_defined?('Followability') && !::Brick.config.table_name_prefixes.key?('followability_')
|
2670
|
-
::Brick.config.table_name_prefixes['followability_'] = 'Followability'
|
2671
|
-
end
|
2672
|
-
end
|
2673
|
-
# Load the initializer for the Apartment gem a little early so that if .excluded_models and
|
2674
|
-
# .default_schema are specified then we can work with non-tenanted models more appropriately
|
2675
|
-
if (apartment = Object.const_defined?('Apartment')) &&
|
2676
|
-
File.exist?(apartment_initializer = ::Rails.root.join('config/initializers/apartment.rb'))
|
2677
|
-
require 'apartment/adapters/abstract_adapter'
|
2678
|
-
Apartment::Adapters::AbstractAdapter.class_exec do
|
2679
|
-
if instance_methods.include?(:process_excluded_models)
|
2680
|
-
def process_excluded_models
|
2681
|
-
# All other models will share a connection (at Apartment.connection_class) and we can modify at will
|
2682
|
-
Apartment.excluded_models.each do |excluded_model|
|
2683
|
-
begin
|
2684
|
-
process_excluded_model(excluded_model)
|
2685
|
-
rescue NameError => e
|
2686
|
-
(@bad_models ||= []) << excluded_model
|
2687
|
-
end
|
2688
|
-
end
|
2689
|
-
end
|
2690
|
-
end
|
2691
|
-
end
|
2692
|
-
unless @_apartment_loaded
|
2693
|
-
load apartment_initializer
|
2694
|
-
@_apartment_loaded = true
|
2695
|
-
end
|
2696
|
-
end
|
2697
|
-
# Only for Postgres (Doesn't work in sqlite3 or MySQL)
|
2698
|
-
# puts ActiveRecord::Base.execute_sql("SELECT current_setting('SEARCH_PATH')").to_a.inspect
|
2699
|
-
|
2700
|
-
# ---------------------------
|
2701
|
-
# 2. Figure out schema things
|
2702
|
-
is_postgres = nil
|
2703
|
-
is_mssql = ActiveRecord::Base.connection.adapter_name == 'SQLServer'
|
2704
|
-
case ActiveRecord::Base.connection.adapter_name
|
2705
|
-
when 'PostgreSQL', 'SQLServer'
|
2706
|
-
is_postgres = !is_mssql
|
2707
|
-
db_schemas = if is_postgres
|
2708
|
-
ActiveRecord::Base.execute_sql('SELECT nspname AS table_schema, MAX(oid) AS dt FROM pg_namespace GROUP BY 1 ORDER BY 1;')
|
2709
|
-
else
|
2710
|
-
ActiveRecord::Base.execute_sql('SELECT DISTINCT table_schema, NULL AS dt FROM INFORMATION_SCHEMA.tables;')
|
2711
|
-
end
|
2712
|
-
::Brick.db_schemas = db_schemas.each_with_object({}) do |row, s|
|
2713
|
-
row = case row
|
2714
|
-
when Array
|
2715
|
-
row
|
2716
|
-
else
|
2717
|
-
[row['table_schema'], row['dt']]
|
2718
|
-
end
|
2719
|
-
# Remove any system schemas
|
2720
|
-
s[row.first] = { dt: row.last } unless ['information_schema', 'pg_catalog', 'pg_toast', 'heroku_ext',
|
2721
|
-
'INFORMATION_SCHEMA', 'sys'].include?(row.first)
|
2722
|
-
end
|
2723
|
-
possible_schema, possible_schemas, multitenancy = ::Brick.get_possible_schemas
|
2724
|
-
if possible_schemas
|
2725
|
-
if possible_schema
|
2726
|
-
::Brick.default_schema = ::Brick.apartment_default_tenant
|
2727
|
-
schema = possible_schema
|
2728
|
-
orig_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',')
|
2729
|
-
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
|
2730
|
-
# When testing, just find the most recently-created schema
|
2731
|
-
elsif begin
|
2732
|
-
Rails.env == 'test' ||
|
2733
|
-
ActiveRecord::Base.execute_sql("SELECT value FROM ar_internal_metadata WHERE key='environment';").first&.fetch('value', nil) == 'test'
|
2734
|
-
rescue
|
2735
|
-
end
|
2736
|
-
::Brick.default_schema = ::Brick.apartment_default_tenant
|
2737
|
-
::Brick.test_schema = schema = ::Brick.db_schemas.to_a.sort { |a, b| b.last[:dt] <=> a.last[:dt] }.first.first
|
2738
|
-
if possible_schema.blank?
|
2739
|
-
puts "While running tests, using the most recently-created schema, #{schema}."
|
2740
|
-
else
|
2741
|
-
puts "While running tests, had noticed in the brick.rb initializer that the line \"::Brick.schema_behavior = ...\" refers to a schema called \"#{possible_schema}\" which does not exist. Reading table structure from the most recently-created schema, #{schema}."
|
2742
|
-
end
|
2743
|
-
orig_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',')
|
2744
|
-
::Brick.config.schema_behavior = { multitenant: {} } # schema_to_analyse: [schema]
|
2745
|
-
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
|
2746
|
-
else
|
2747
|
-
puts "*** In the brick.rb initializer the line \"::Brick.schema_behavior = ...\" refers to schema(s) called #{possible_schemas.map { |s| "\"#{s}\"" }.join(', ')}. No mentioned schema exists. ***"
|
2748
|
-
if ::Brick.db_schemas.key?(::Brick.apartment_default_tenant)
|
2749
|
-
::Brick.default_schema = schema = ::Brick.apartment_default_tenant
|
2750
|
-
end
|
2751
|
-
end
|
2752
|
-
end
|
2753
|
-
when 'Mysql2', 'Trilogy'
|
2754
|
-
::Brick.default_schema = schema = ActiveRecord::Base.connection.current_database
|
2755
|
-
when 'OracleEnhanced'
|
2756
|
-
# ActiveRecord::Base.connection.current_database will be something like "XEPDB1"
|
2757
|
-
::Brick.default_schema = schema = ActiveRecord::Base.connection.raw_connection.username
|
2758
|
-
::Brick.db_schemas = {}
|
2759
|
-
ActiveRecord::Base.execute_sql("SELECT username FROM sys.all_users WHERE ORACLE_MAINTAINED != 'Y'").each { |s| ::Brick.db_schemas[s.first] = {} }
|
2760
|
-
when 'SQLite'
|
2761
|
-
sql = "SELECT m.name AS relation_name, UPPER(m.type) AS table_type,
|
2762
|
-
p.name AS column_name, p.type AS data_type,
|
2763
|
-
CASE p.pk WHEN 1 THEN 'PRIMARY KEY' END AS const
|
2764
|
-
FROM sqlite_master AS m
|
2765
|
-
INNER JOIN pragma_table_info(m.name) AS p
|
2766
|
-
WHERE m.name NOT IN ('sqlite_sequence', ?, ?)
|
2767
|
-
ORDER BY m.name, p.cid"
|
2768
|
-
else
|
2769
|
-
puts "Unfamiliar with connection adapter #{ActiveRecord::Base.connection.adapter_name}"
|
2770
|
-
end
|
2771
|
-
|
2772
|
-
::Brick.db_schemas ||= {}
|
2773
|
-
|
2774
|
-
# ---------------------
|
2775
|
-
# 3. Tables and columns
|
2776
|
-
# %%% Retrieve internal ActiveRecord table names like this:
|
2777
|
-
# ActiveRecord::Base.internal_metadata_table_name, ActiveRecord::Base.schema_migrations_table_name
|
2778
|
-
# For if it's not SQLite -- so this is the Postgres and MySQL version
|
2779
|
-
measures = []
|
2780
|
-
::Brick.is_oracle = true if ActiveRecord::Base.connection.adapter_name == 'OracleEnhanced'
|
2781
|
-
case ActiveRecord::Base.connection.adapter_name
|
2782
|
-
when 'PostgreSQL', 'SQLite' # These bring back a hash for each row because the query uses column aliases
|
2783
|
-
# schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
2784
|
-
retrieve_schema_and_tables(sql, is_postgres, is_mssql, schema).each do |r|
|
2785
|
-
# If Apartment gem lists the table as being associated with a non-tenanted model then use whatever it thinks
|
2786
|
-
# is the default schema, usually 'public'.
|
2787
|
-
schema_name = if ::Brick.config.schema_behavior[:multitenant]
|
2788
|
-
::Brick.apartment_default_tenant if ::Brick.is_apartment_excluded_table(r['relation_name'])
|
2789
|
-
elsif ![schema, 'public'].include?(r['schema'])
|
2790
|
-
r['schema']
|
2791
|
-
end
|
2792
|
-
relation_name = schema_name ? "#{schema_name}.#{r['relation_name']}" : r['relation_name']
|
2793
|
-
# Both uppers and lowers as well as underscores?
|
2794
|
-
::Brick.apply_double_underscore_patch if relation_name =~ /[A-Z]/ && relation_name =~ /[a-z]/ && relation_name.index('_')
|
2795
|
-
relation = relations[relation_name]
|
2796
|
-
relation[:isView] = true if r['table_type'] == 'VIEW'
|
2797
|
-
relation[:description] = r['table_description'] if r['table_description']
|
2798
|
-
col_name = r['column_name']
|
2799
|
-
key = case r['const']
|
2800
|
-
when 'PRIMARY KEY'
|
2801
|
-
relation[:pkey][r['key'] || relation_name] ||= []
|
2802
|
-
when 'UNIQUE'
|
2803
|
-
relation[:ukeys][r['key'] || "#{relation_name}.#{col_name}"] ||= []
|
2804
|
-
# key = (relation[:ukeys] = Hash.new { |h, k| h[k] = [] }) if key.is_a?(Array)
|
2805
|
-
# key[r['key']]
|
2806
|
-
else
|
2807
|
-
if r['data_type'] == 'uuid'
|
2808
|
-
# && r['column_name'] == ::Brick.ar_base.primary_key
|
2809
|
-
# binding.pry
|
2810
|
-
relation[:pkey][r['key'] || relation_name] ||= []
|
2811
|
-
end
|
2812
|
-
end
|
2813
|
-
# binding.pry if key && r['data_type'] == 'uuid'
|
2814
|
-
key << col_name if key
|
2815
|
-
cols = relation[:cols] # relation.fetch(:cols) { relation[:cols] = [] }
|
2816
|
-
cols[col_name] = [r['data_type'], r['max_length'], measures&.include?(col_name), r['is_nullable'] == 'NO']
|
2817
|
-
# puts "KEY! #{r['relation_name']}.#{col_name} #{r['key']} #{r['const']}" if r['key']
|
2818
|
-
relation[:col_descrips][col_name] = r['column_description'] if r['column_description']
|
2819
|
-
end
|
2820
|
-
else # MySQL2, OracleEnhanced, and MSSQL act a little differently, bringing back an array for each row
|
2821
|
-
schema_and_tables = case ActiveRecord::Base.connection.adapter_name
|
2822
|
-
when 'OracleEnhanced'
|
2823
|
-
sql =
|
2824
|
-
"SELECT c.owner AS schema, c.table_name AS relation_name,
|
2825
|
-
CASE WHEN v.owner IS NULL THEN 'BASE_TABLE' ELSE 'VIEW' END AS table_type,
|
2826
|
-
c.column_name, c.data_type,
|
2827
|
-
COALESCE(c.data_length, c.data_precision) AS max_length,
|
2828
|
-
CASE ac.constraint_type WHEN 'P' THEN 'PRIMARY KEY' END AS const,
|
2829
|
-
ac.constraint_name AS \"key\",
|
2830
|
-
CASE c.nullable WHEN 'Y' THEN 'YES' ELSE 'NO' END AS is_nullable
|
2831
|
-
FROM all_tab_cols c
|
2832
|
-
LEFT OUTER JOIN all_cons_columns acc ON acc.owner = c.owner AND acc.table_name = c.table_name AND acc.column_name = c.column_name
|
2833
|
-
LEFT OUTER JOIN all_constraints ac ON ac.owner = acc.owner AND ac.table_name = acc.table_name AND ac.constraint_name = acc.constraint_name AND constraint_type = 'P'
|
2834
|
-
LEFT OUTER JOIN all_views v ON c.owner = v.owner AND c.table_name = v.view_name
|
2835
|
-
WHERE c.owner IN (#{::Brick.db_schemas.keys.map { |s| "'#{s}'" }.join(', ')})
|
2836
|
-
AND c.table_name NOT IN (?, ?)
|
2837
|
-
ORDER BY 1, 2, c.internal_column_id, acc.position"
|
2838
|
-
ActiveRecord::Base.execute_sql(sql, *ar_tables)
|
2839
|
-
else
|
2840
|
-
retrieve_schema_and_tables(sql)
|
2841
|
-
end
|
2842
|
-
|
2843
|
-
schema_and_tables.each do |r|
|
2844
|
-
next if r[1].index('$') # Oracle can have goofy table names with $
|
2845
|
-
|
2846
|
-
if (relation_name = r[1]) =~ /^[A-Z0-9_]+$/
|
2847
|
-
relation_name.downcase!
|
2848
|
-
# Both uppers and lowers as well as underscores?
|
2849
|
-
elsif relation_name =~ /[A-Z]/ && relation_name =~ /[a-z]/ && relation_name.index('_')
|
2850
|
-
::Brick.apply_double_underscore_patch
|
2851
|
-
end
|
2852
|
-
# Expect the default schema for SQL Server to be 'dbo'.
|
2853
|
-
if (::Brick.is_oracle && r[0] != schema) || (is_mssql && r[0] != 'dbo')
|
2854
|
-
relation_name = "#{r[0]}.#{relation_name}"
|
2855
|
-
end
|
2856
|
-
|
2857
|
-
relation = relations[relation_name] # here relation represents a table or view from the database
|
2858
|
-
relation[:isView] = true if r[2] == 'VIEW' # table_type
|
2859
|
-
col_name = ::Brick.is_oracle ? connection.send(:oracle_downcase, r[3]) : r[3]
|
2860
|
-
key = case r[6] # constraint type
|
2861
|
-
when 'PRIMARY KEY'
|
2862
|
-
# key
|
2863
|
-
relation[:pkey][r[7] || relation_name] ||= []
|
2864
|
-
when 'UNIQUE'
|
2865
|
-
relation[:ukeys][r[7] || "#{relation_name}.#{col_name}"] ||= []
|
2866
|
-
# key = (relation[:ukeys] = Hash.new { |h, k| h[k] = [] }) if key.is_a?(Array)
|
2867
|
-
# key[r['key']]
|
2868
|
-
end
|
2869
|
-
key << col_name if key
|
2870
|
-
cols = relation[:cols] # relation.fetch(:cols) { relation[:cols] = [] }
|
2871
|
-
# 'data_type', 'max_length', measure, 'is_nullable'
|
2872
|
-
cols[col_name] = [r[4], r[5], measures&.include?(col_name), r[8] == 'NO']
|
2873
|
-
# puts "KEY! #{r['relation_name']}.#{col_name} #{r['key']} #{r['const']}" if r['key']
|
2874
|
-
end
|
2875
|
-
end
|
2876
|
-
|
2877
|
-
# PostGIS adds three views which would confuse Rails if models were to be built for them.
|
2878
|
-
if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
2879
|
-
if relations.key?('geography_columns') && relations.key?('geometry_columns') && relations.key?('spatial_ref_sys')
|
2880
|
-
(::Brick.config.exclude_tables ||= []) << 'geography_columns'
|
2881
|
-
::Brick.config.exclude_tables << 'geometry_columns'
|
2882
|
-
::Brick.config.exclude_tables << 'spatial_ref_sys'
|
2883
|
-
end
|
2884
|
-
end
|
2885
|
-
|
2886
|
-
# # Add unique OIDs
|
2887
|
-
# if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
2888
|
-
# ActiveRecord::Base.execute_sql(
|
2889
|
-
# "SELECT c.oid, n.nspname, c.relname
|
2890
|
-
# FROM pg_catalog.pg_namespace AS n
|
2891
|
-
# INNER JOIN pg_catalog.pg_class AS c ON n.oid = c.relnamespace
|
2892
|
-
# WHERE c.relkind IN ('r', 'v')"
|
2893
|
-
# ).each do |r|
|
2894
|
-
# next if ['pg_catalog', 'information_schema', ''].include?(r['nspname']) ||
|
2895
|
-
# ['ar_internal_metadata', 'schema_migrations'].include?(r['relname'])
|
2896
|
-
# relation = relations.fetch(r['relname'], nil)
|
2897
|
-
# if relation
|
2898
|
-
# (relation[:oid] ||= {})[r['nspname']] = r['oid']
|
2899
|
-
# else
|
2900
|
-
# puts "Where is #{r['nspname']} #{r['relname']} ?"
|
2901
|
-
# end
|
2902
|
-
# end
|
2903
|
-
# end
|
2904
|
-
# schema = ::Brick.default_schema # Reset back for this next round of fun
|
2905
|
-
|
2906
|
-
# ---------------------------------------------
|
2907
|
-
# 4. Foreign key info
|
2908
|
-
# (done in two parts which get JOINed together in Ruby code)
|
2909
|
-
kcus = nil
|
2910
|
-
entry_type = nil
|
2911
|
-
case ActiveRecord::Base.connection.adapter_name
|
2912
|
-
when 'PostgreSQL', 'Mysql2', 'Trilogy', 'SQLServer'
|
2913
|
-
# Part 1 -- all KCUs
|
2914
|
-
sql = "SELECT CONSTRAINT_CATALOG, CONSTRAINT_SCHEMA, CONSTRAINT_NAME, ORDINAL_POSITION,
|
2915
|
-
TABLE_NAME, COLUMN_NAME
|
2916
|
-
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE#{"
|
2917
|
-
WHERE CONSTRAINT_SCHEMA = COALESCE(current_setting('SEARCH_PATH'), 'public')" if is_postgres && schema }#{"
|
2918
|
-
WHERE CONSTRAINT_SCHEMA = '#{ActiveRecord::Base.connection.current_database&.tr("'", "''")}'" if is_mysql
|
2919
|
-
}"
|
2920
|
-
kcus = ActiveRecord::Base.execute_sql(sql).each_with_object({}) do |v, s|
|
2921
|
-
if (entry_type ||= v.is_a?(Array) ? :array : :hash) == :hash
|
2922
|
-
key = "#{v['constraint_name']}.#{v['constraint_schema']}.#{v['constraint_catalog']}.#{v['ordinal_position']}"
|
2923
|
-
key << ".#{v['table_name']}.#{v['column_name']}" unless is_postgres || is_mssql
|
2924
|
-
s[key] = [v['constraint_schema'], v['table_name']]
|
2925
|
-
else # Array
|
2926
|
-
key = "#{v[2]}.#{v[1]}.#{v[0]}.#{v[3]}"
|
2927
|
-
key << ".#{v[4]}.#{v[5]}" unless is_postgres || is_mssql
|
2928
|
-
s[key] = [v[1], v[4]]
|
2929
|
-
end
|
2930
|
-
end
|
2931
|
-
|
2932
|
-
# Part 2 -- fk_references
|
2933
|
-
sql = "SELECT kcu.CONSTRAINT_SCHEMA, kcu.TABLE_NAME, kcu.COLUMN_NAME,
|
2934
|
-
#{# These will get filled in with real values (effectively doing the JOIN in Ruby)
|
2935
|
-
is_postgres || is_mssql ? 'NULL as primary_schema, NULL as primary_table' :
|
2936
|
-
'kcu.REFERENCED_TABLE_NAME, kcu.REFERENCED_COLUMN_NAME'},
|
2937
|
-
kcu.CONSTRAINT_NAME AS CONSTRAINT_SCHEMA_FK,
|
2938
|
-
rc.UNIQUE_CONSTRAINT_NAME, rc.UNIQUE_CONSTRAINT_SCHEMA, rc.UNIQUE_CONSTRAINT_CATALOG, kcu.ORDINAL_POSITION
|
2939
|
-
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS rc
|
2940
|
-
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu
|
2941
|
-
ON kcu.CONSTRAINT_CATALOG = rc.CONSTRAINT_CATALOG
|
2942
|
-
AND kcu.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA
|
2943
|
-
AND kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME#{"
|
2944
|
-
WHERE kcu.CONSTRAINT_SCHEMA = COALESCE(current_setting('SEARCH_PATH'), 'public')" if is_postgres && schema}#{"
|
2945
|
-
WHERE kcu.CONSTRAINT_SCHEMA = '#{ActiveRecord::Base.connection.current_database&.tr("'", "''")}'" if is_mysql}"
|
2946
|
-
fk_references = ActiveRecord::Base.execute_sql(sql)
|
2947
|
-
when 'SQLite'
|
2948
|
-
sql = "SELECT NULL AS constraint_schema, m.name, fkl.\"from\", NULL AS primary_schema, fkl.\"table\", m.name || '_' || fkl.\"from\" AS constraint_name
|
2949
|
-
FROM sqlite_master m
|
2950
|
-
INNER JOIN pragma_foreign_key_list(m.name) fkl ON m.type = 'table'
|
2951
|
-
ORDER BY m.name, fkl.seq"
|
2952
|
-
fk_references = ActiveRecord::Base.execute_sql(sql)
|
2953
|
-
when 'OracleEnhanced'
|
2954
|
-
schemas = ::Brick.db_schemas.keys.map { |s| "'#{s}'" }.join(', ')
|
2955
|
-
sql =
|
2956
|
-
"SELECT -- fk
|
2957
|
-
ac.owner AS constraint_schema, acc_fk.table_name, acc_fk.column_name,
|
2958
|
-
-- referenced pk
|
2959
|
-
ac.r_owner AS primary_schema, acc_pk.table_name AS primary_table, acc_fk.constraint_name AS constraint_schema_fk
|
2960
|
-
-- , acc_pk.column_name
|
2961
|
-
FROM all_cons_columns acc_fk
|
2962
|
-
INNER JOIN all_constraints ac ON acc_fk.owner = ac.owner
|
2963
|
-
AND acc_fk.constraint_name = ac.constraint_name
|
2964
|
-
INNER JOIN all_cons_columns acc_pk ON ac.r_owner = acc_pk.owner
|
2965
|
-
AND ac.r_constraint_name = acc_pk.constraint_name
|
2966
|
-
WHERE ac.constraint_type = 'R'
|
2967
|
-
AND ac.owner IN (#{schemas})
|
2968
|
-
AND ac.r_owner IN (#{schemas})"
|
2969
|
-
fk_references = ActiveRecord::Base.execute_sql(sql)
|
2970
|
-
end
|
2971
|
-
::Brick.is_oracle = true if ActiveRecord::Base.connection.adapter_name == 'OracleEnhanced'
|
2972
|
-
# ::Brick.default_schema ||= schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
2973
|
-
::Brick.default_schema ||= 'public' if is_postgres
|
2974
|
-
fk_references&.each do |fk|
|
2975
|
-
fk = fk.values unless fk.is_a?(Array)
|
2976
|
-
# Virtually JOIN KCUs to fk_references in order to fill in the primary schema and primary table
|
2977
|
-
kcu_key = "#{fk[6]}.#{fk[7]}.#{fk[8]}.#{fk[9]}"
|
2978
|
-
kcu_key << ".#{fk[3]}.#{fk[4]}" unless is_postgres || is_mssql
|
2979
|
-
if (kcu = kcus&.fetch(kcu_key, nil))
|
2980
|
-
fk[3] = kcu[0]
|
2981
|
-
fk[4] = kcu[1]
|
2982
|
-
end
|
2983
|
-
# Multitenancy makes things a little more general overall, except for non-tenanted tables
|
2984
|
-
if ::Brick.is_apartment_excluded_table(::Brick.namify(fk[1]))
|
2985
|
-
fk[0] = ::Brick.apartment_default_tenant
|
2986
|
-
elsif (is_postgres && (fk[0] == 'public' || (multitenancy && fk[0] == schema))) ||
|
2987
|
-
(::Brick.is_oracle && fk[0] == schema) ||
|
2988
|
-
(is_mssql && fk[0] == 'dbo') ||
|
2989
|
-
(!is_postgres && !::Brick.is_oracle && !is_mssql && ['mysql', 'performance_schema', 'sys'].exclude?(fk[0]))
|
2990
|
-
fk[0] = nil
|
2991
|
-
end
|
2992
|
-
if ::Brick.is_apartment_excluded_table(fk[4])
|
2993
|
-
fk[3] = ::Brick.apartment_default_tenant
|
2994
|
-
elsif (is_postgres && (fk[3] == 'public' || (multitenancy && fk[3] == schema))) ||
|
2995
|
-
(::Brick.is_oracle && fk[3] == schema) ||
|
2996
|
-
(is_mssql && fk[3] == 'dbo') ||
|
2997
|
-
(!is_postgres && !::Brick.is_oracle && !is_mssql && ['mysql', 'performance_schema', 'sys'].exclude?(fk[3]))
|
2998
|
-
fk[3] = nil
|
2999
|
-
end
|
3000
|
-
if ::Brick.is_oracle
|
3001
|
-
fk[1].downcase! if fk[1] =~ /^[A-Z0-9_]+$/
|
3002
|
-
fk[4].downcase! if fk[4] =~ /^[A-Z0-9_]+$/
|
3003
|
-
fk[2] = connection.send(:oracle_downcase, fk[2])
|
3004
|
-
end
|
3005
|
-
::Brick._add_bt_and_hm(fk, relations)
|
3006
|
-
end
|
3007
|
-
kcus = nil # Allow this large item to be garbage collected
|
3008
|
-
end
|
3009
|
-
|
3010
|
-
table_name_lookup = (::Brick.table_name_lookup ||= {})
|
3011
|
-
relations.each do |k, v|
|
3012
|
-
next if k.is_a?(Symbol)
|
3013
|
-
|
3014
|
-
rel_name = k.split('.').map { |rel_part| ::Brick.namify(rel_part, :underscore) }
|
3015
|
-
schema_names = rel_name[0..-2]
|
3016
|
-
schema_names.shift if ::Brick.apartment_multitenant && schema_names.first == ::Brick.apartment_default_tenant
|
3017
|
-
v[:schema] = schema_names.join('.') unless schema_names.empty?
|
3018
|
-
# %%% If more than one schema has the same table name, will need to add a schema name prefix to have uniqueness
|
3019
|
-
if (singular = rel_name.last.singularize).blank?
|
3020
|
-
singular = rel_name.last
|
3021
|
-
end
|
3022
|
-
name_parts = if (tnp = ::Brick.config.table_name_prefixes
|
3023
|
-
&.find { |k1, _v1| singular.start_with?(k1) && singular.length > k1.length }
|
3024
|
-
).present?
|
3025
|
-
v[:auto_prefixed_schema] = tnp.first
|
3026
|
-
# v[:resource] = rel_name.last[tnp.first.length..-1]
|
3027
|
-
[tnp.last, singular[tnp.first.length..-1]]
|
3028
|
-
else
|
3029
|
-
# v[:resource] = rel_name.last
|
3030
|
-
[singular]
|
3031
|
-
end
|
3032
|
-
proposed_name_parts = (schema_names + name_parts).map { |p| ::Brick.namify(p, :underscore).camelize }
|
3033
|
-
# Find out if the proposed name leads to a module or class that already exists and is not an AR class
|
3034
|
-
colliding_thing = nil
|
3035
|
-
loop do
|
3036
|
-
klass = Object
|
3037
|
-
proposed_name_parts.each do |part|
|
3038
|
-
if klass.const_defined?(part)
|
3039
|
-
klass = klass.const_get(part)
|
3040
|
-
else
|
3041
|
-
klass = nil
|
3042
|
-
break
|
3043
|
-
end
|
3044
|
-
end
|
3045
|
-
break if !klass || (klass < ActiveRecord::Base) # Break if all good -- no conflicts
|
3046
|
-
|
3047
|
-
# Find a unique name since there's already something that's non-AR with that same name
|
3048
|
-
last_idx = proposed_name_parts.length - 1
|
3049
|
-
proposed_name_parts[last_idx] = ::Brick.ensure_unique(proposed_name_parts[last_idx], 'X')
|
3050
|
-
colliding_thing ||= klass
|
3051
|
-
end
|
3052
|
-
v[:class_name] = proposed_name_parts.join('::')
|
3053
|
-
# Was: v[:resource] = v[:class_name].underscore.tr('/', '.')
|
3054
|
-
v[:resource] = proposed_name_parts.last.underscore
|
3055
|
-
if colliding_thing
|
3056
|
-
message_start = if colliding_thing.is_a?(Module) && Object.const_defined?(:Rails) &&
|
3057
|
-
colliding_thing.constants.find { |c| colliding_thing.const_get(c) < Rails::Application }
|
3058
|
-
"The module for the Rails application itself, \"#{colliding_thing.name}\","
|
3059
|
-
else
|
3060
|
-
"Non-AR #{colliding_thing.class.name.downcase} \"#{colliding_thing.name}\""
|
3061
|
-
end
|
3062
|
-
puts "WARNING: #{message_start} already exists.\n Will set up to auto-create model #{v[:class_name]} for table #{k}."
|
3063
|
-
end
|
3064
|
-
# Track anything that's out-of-the-ordinary
|
3065
|
-
table_name_lookup[v[:class_name]] = k unless v[:class_name].underscore.pluralize == k
|
3066
|
-
end
|
3067
|
-
::Brick.load_additional_references if ::Brick.initializer_loaded
|
3068
|
-
|
3069
|
-
if is_postgres
|
3070
|
-
params = []
|
3071
|
-
ActiveRecord::Base.execute_sql("-- inherited and partitioned tables counts
|
3072
|
-
SELECT n.nspname, parent.relname,
|
3073
|
-
((SUM(child.reltuples::float) / greatest(SUM(child.relpages), 1))) *
|
3074
|
-
(SUM(pg_relation_size(child.oid))::float / (current_setting('block_size')::float))::integer AS rowcount
|
3075
|
-
FROM pg_inherits
|
3076
|
-
INNER JOIN pg_class parent ON pg_inherits.inhparent = parent.oid
|
3077
|
-
INNER JOIN pg_class child ON pg_inherits.inhrelid = child.oid
|
3078
|
-
INNER JOIN pg_catalog.pg_namespace n ON n.oid = parent.relnamespace#{
|
3079
|
-
if schema
|
3080
|
-
params << schema
|
3081
|
-
"
|
3082
|
-
WHERE n.nspname = COALESCE(?, 'public')"
|
3083
|
-
end}
|
3084
|
-
GROUP BY n.nspname, parent.relname, child.reltuples, child.relpages, child.oid
|
3085
|
-
|
3086
|
-
UNION ALL
|
3087
|
-
|
3088
|
-
-- table count
|
3089
|
-
SELECT n.nspname, pg_class.relname,
|
3090
|
-
(pg_class.reltuples::float / greatest(pg_class.relpages, 1)) *
|
3091
|
-
(pg_relation_size(pg_class.oid)::float / (current_setting('block_size')::float))::integer AS rowcount
|
3092
|
-
FROM pg_class
|
3093
|
-
INNER JOIN pg_catalog.pg_namespace n ON n.oid = pg_class.relnamespace#{
|
3094
|
-
if schema
|
3095
|
-
params << schema
|
3096
|
-
"
|
3097
|
-
WHERE n.nspname = COALESCE(?, 'public')"
|
3098
|
-
end}
|
3099
|
-
GROUP BY n.nspname, pg_class.relname, pg_class.reltuples, pg_class.relpages, pg_class.oid", params).each do |tblcount|
|
3100
|
-
# %%% What is the default schema here?
|
3101
|
-
prefix = "#{tblcount['nspname']}." unless tblcount['nspname'] == (schema || 'public')
|
3102
|
-
relations.fetch("#{prefix}#{tblcount['relname']}", nil)&.[]=(:rowcount, tblcount['rowcount'].to_i.round)
|
2669
|
+
begin
|
2670
|
+
# ::Brick.is_db_present = true
|
2671
|
+
::Brick.reflect_tables
|
2672
|
+
rescue ActiveRecord::NoDatabaseError
|
2673
|
+
# ::Brick.is_db_present = false
|
3103
2674
|
end
|
2675
|
+
conn
|
3104
2676
|
end
|
3105
|
-
|
3106
|
-
if orig_schema && (orig_schema = (orig_schema - ['pg_catalog', 'pg_toast', 'heroku_ext']).first)
|
3107
|
-
puts "Now switching back to \"#{orig_schema}\" schema."
|
3108
|
-
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", orig_schema)
|
3109
|
-
end
|
3110
|
-
end
|
3111
|
-
|
3112
|
-
def retrieve_schema_and_tables(sql = nil, is_postgres = nil, is_mssql = nil, schema = nil)
|
3113
|
-
is_mssql = ActiveRecord::Base.connection.adapter_name == 'SQLServer' if is_mssql.nil?
|
3114
|
-
params = ar_tables
|
3115
|
-
sql ||= "SELECT t.table_schema AS \"schema\", t.table_name AS relation_name, t.table_type,#{"
|
3116
|
-
pg_catalog.obj_description(
|
3117
|
-
('\"' || t.table_schema || '\".\"' || t.table_name || '\"')::regclass::oid, 'pg_class'
|
3118
|
-
) AS table_description,
|
3119
|
-
pg_catalog.col_description(
|
3120
|
-
('\"' || t.table_schema || '\".\"' || t.table_name || '\"')::regclass::oid, c.ordinal_position
|
3121
|
-
) AS column_description," if is_postgres}
|
3122
|
-
c.column_name, #{is_postgres ? "CASE c.data_type WHEN 'USER-DEFINED' THEN pg_t.typname ELSE c.data_type END AS data_type" : 'c.data_type'},
|
3123
|
-
COALESCE(c.character_maximum_length, c.numeric_precision) AS max_length,
|
3124
|
-
kcu.constraint_type AS const, kcu.constraint_name AS \"key\",
|
3125
|
-
c.is_nullable
|
3126
|
-
FROM INFORMATION_SCHEMA.tables AS t
|
3127
|
-
LEFT OUTER JOIN INFORMATION_SCHEMA.columns AS c ON t.table_schema = c.table_schema
|
3128
|
-
AND t.table_name = c.table_name
|
3129
|
-
LEFT OUTER JOIN
|
3130
|
-
(SELECT kcu1.constraint_schema, kcu1.table_name, kcu1.column_name, kcu1.ordinal_position,
|
3131
|
-
tc.constraint_type, kcu1.constraint_name
|
3132
|
-
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu1
|
3133
|
-
INNER JOIN INFORMATION_SCHEMA.table_constraints AS tc
|
3134
|
-
ON kcu1.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA
|
3135
|
-
AND kcu1.TABLE_NAME = tc.TABLE_NAME
|
3136
|
-
AND kcu1.CONSTRAINT_NAME = tc.constraint_name
|
3137
|
-
AND tc.constraint_type != 'FOREIGN KEY' -- For MSSQL
|
3138
|
-
) AS kcu ON
|
3139
|
-
-- kcu.CONSTRAINT_CATALOG = t.table_catalog AND
|
3140
|
-
kcu.CONSTRAINT_SCHEMA = c.table_schema
|
3141
|
-
AND kcu.TABLE_NAME = c.table_name
|
3142
|
-
AND kcu.column_name = c.column_name#{"
|
3143
|
-
-- AND kcu.position_in_unique_constraint IS NULL" unless is_mssql}#{"
|
3144
|
-
INNER JOIN pg_catalog.pg_namespace pg_n ON pg_n.nspname = t.table_schema
|
3145
|
-
INNER JOIN pg_catalog.pg_class pg_c ON pg_n.oid = pg_c.relnamespace AND pg_c.relname = c.table_name
|
3146
|
-
INNER JOIN pg_catalog.pg_attribute pg_a ON pg_c.oid = pg_a.attrelid AND pg_a.attname = c.column_name
|
3147
|
-
INNER JOIN pg_catalog.pg_type pg_t ON pg_t.oid = pg_a.atttypid" if is_postgres}
|
3148
|
-
WHERE t.table_schema #{is_postgres || is_mssql ?
|
3149
|
-
"NOT IN ('information_schema', 'pg_catalog', 'pg_toast', 'heroku_ext',
|
3150
|
-
'INFORMATION_SCHEMA', 'sys')"
|
3151
|
-
:
|
3152
|
-
"= '#{ActiveRecord::Base.connection.current_database&.tr("'", "''")}'"}#{
|
3153
|
-
if is_postgres && schema
|
3154
|
-
params = params.unshift(schema) # Used to use this SQL: current_setting('SEARCH_PATH')
|
3155
|
-
"
|
3156
|
-
AND t.table_schema = COALESCE(?, 'public')"
|
3157
|
-
end}
|
3158
|
-
-- AND t.table_type IN ('VIEW') -- 'BASE TABLE', 'FOREIGN TABLE'
|
3159
|
-
AND t.table_name NOT IN ('pg_stat_statements', ?, ?)
|
3160
|
-
ORDER BY 1, t.table_type DESC, 2, c.ordinal_position"
|
3161
|
-
ActiveRecord::Base.execute_sql(sql, *params)
|
3162
|
-
end
|
3163
|
-
|
3164
|
-
def ar_tables
|
3165
|
-
ar_smtn = if ActiveRecord::Base.respond_to?(:schema_migrations_table_name)
|
3166
|
-
ActiveRecord::Base.schema_migrations_table_name
|
3167
|
-
else
|
3168
|
-
'schema_migrations'
|
3169
|
-
end
|
3170
|
-
ar_imtn = ActiveRecord.version >= ::Gem::Version.new('5.0') ? ActiveRecord::Base.internal_metadata_table_name : 'ar_internal_metadata'
|
3171
|
-
[ar_smtn, ar_imtn]
|
3172
2677
|
end
|
3173
2678
|
end
|
3174
|
-
|
3175
2679
|
# ==========================================
|
3176
2680
|
|
3177
2681
|
# :nodoc:
|
@@ -3464,7 +2968,7 @@ module Brick
|
|
3464
2968
|
end
|
3465
2969
|
end
|
3466
2970
|
|
3467
|
-
def _brick_index(tbl_name, mode = nil, separator = nil, relation = nil)
|
2971
|
+
def _brick_index(tbl_name, mode = nil, separator = nil, relation = nil, not_path = nil)
|
3468
2972
|
separator ||= '_'
|
3469
2973
|
res_name = (tbl_name_parts = tbl_name.split('.'))[0..-2].first
|
3470
2974
|
res_name << '.' if res_name
|
@@ -3472,22 +2976,28 @@ module Brick
|
|
3472
2976
|
|
3473
2977
|
res_parts = ((mode == :singular) ? res_name.singularize : res_name).split('.')
|
3474
2978
|
res_parts.shift if ::Brick.apartment_multitenant && res_parts.length > 1 && res_parts.first == ::Brick.apartment_default_tenant
|
2979
|
+
index2 = []
|
2980
|
+
if ::Brick.config.path_prefix
|
2981
|
+
res_parts.unshift(::Brick.config.path_prefix)
|
2982
|
+
index2 << ::Brick.config.path_prefix
|
2983
|
+
end
|
3475
2984
|
if (aps = relation&.fetch(:auto_prefixed_schema, nil)) # && res_parts.last.start_with?(aps)
|
3476
2985
|
aps = aps[0..-2] if aps[-1] == '_'
|
3477
2986
|
last_part = res_parts.last # [aps.length..-1]
|
3478
2987
|
res_parts[-1] = aps
|
3479
2988
|
res_parts << last_part
|
3480
|
-
|
3481
|
-
path_prefix = []
|
3482
|
-
if ::Brick.config.path_prefix
|
3483
|
-
res_parts.unshift(::Brick.config.path_prefix)
|
3484
|
-
path_prefix << ::Brick.config.path_prefix
|
2989
|
+
index2 << aps
|
3485
2990
|
end
|
3486
2991
|
index = res_parts.map(&:underscore).join(separator)
|
3487
|
-
|
3488
|
-
|
3489
|
-
|
3490
|
-
|
2992
|
+
if separator == 'x'
|
2993
|
+
index = index.tr('_', 'x')
|
2994
|
+
else
|
2995
|
+
# Rails applies an _index suffix to that route when the resource name isn't something plural
|
2996
|
+
index << '_index' if mode != :singular && !not_path &&
|
2997
|
+
index == (
|
2998
|
+
index2 + [relation[:class_name][(relation&.fetch(:auto_prefixed_class, nil)&.length&.+ 2) || 0..-1]&.underscore&.tr('/', '_') || '_']
|
2999
|
+
).join(separator)
|
3000
|
+
end
|
3491
3001
|
index
|
3492
3002
|
end
|
3493
3003
|
|