cartodb-4.42/app/models/carto/user_table.rb

323 lines
9.1 KiB
Ruby
Raw Normal View History

2024-04-06 13:25:13 +08:00
require 'active_record'
require_dependency 'carto/db/sanitize'
# Integer type for PostgreSQL oid columns, with proper range
class Carto::OidType < ActiveRecord::Type::Integer
def max_value
(1 << 32) - 1
end
def min_value
0
end
end
module Carto
class UserTable < ActiveRecord::Base
PRIVACY_PRIVATE = 0
PRIVACY_PUBLIC = 1
PRIVACY_LINK = 2
PRIVACY_VALUES_TO_TEXTS = {
PRIVACY_PRIVATE => 'private',
PRIVACY_PUBLIC => 'public',
PRIVACY_LINK => 'link'
}.freeze
# Column table_id is of type oid and the type casting provided by ActiveRecord is not
# good for it since it only supports signed values
attribute 'table_id', Carto::OidType.new
# AR sets privacy = 0 (private) by default, taken from the DB. We want it to be `nil`
# so the `before_validation` hook sets an appropriate privacy based on the table owner
attribute 'privacy', Type::Integer.new, default: nil
# The ::Table service depends on the constructor not being able to set all parameters, only these are allowed
# This is done so things like name changes are forced to go through ::Table.name= to ensure renaming behaviour
attr_accessible :privacy, :tags, :description
belongs_to :user
belongs_to :map, inverse_of: :user_table
belongs_to :data_import
# Disabled to avoid conflicting with the `tags` field. This relation is updated by ::Table.manage_tags.
# TODO: We can remove both the `user_tables.tags` field and the `tags` table in favour of the canonical viz tags.
# has_many :tags, foreign_key: :table_id
has_many :layers_user_table
has_many :layers, through: :layers_user_table
before_validation :set_default_table_privacy
validates :user, presence: true
validate :validate_user_not_viewer
validates :name, uniqueness: { scope: :user_id }
validates :name, exclusion: Carto::DB::Sanitize::RESERVED_TABLE_NAMES
validates :privacy, inclusion: [PRIVACY_PRIVATE, PRIVACY_PUBLIC, PRIVACY_LINK].freeze
validate :validate_privacy_changes
before_create :service_before_create
after_create :create_canonical_visualization, unless: :map
after_create :service_after_create
after_save :service_after_save
# The `destroyed?` check is needed to avoid the hook running twice when deleting a table from the ::Table service
# as it is triggered directly, and a second time from canonical visualization destruction hooks.
# TODO: This can be simplified after deleting the old UserTable model
before_destroy :ensure_not_viewer
before_destroy :cache_dependent_visualizations, unless: :destroyed?
after_destroy :destroy_dependent_visualizations
after_destroy :service_after_destroy
def geometry_types
@geometry_types ||= service.geometry_types
end
# Estimated size
def size
row_count_and_size[:size]
end
def table_size
service.table_size
end
# Estimated row_count. Preferred: `estimated_row_count`
def row_count
row_count_and_size[:row_count]
end
# Estimated row count and size. Preferred `estimated_row_count` for row count.
def row_count_and_size
@row_count_and_size ||= service.row_count_and_size
end
def service
@service ||= ::Table.new(user_table: self)
end
def set_service(table)
@service = table
end
def visualization
map.visualization if map
end
def synchronization
visualization.synchronization if visualization
end
def fully_dependent_visualizations
affected_visualizations.select { |v| v.fully_dependent_on?(self) }
end
def accessible_dependent_derived_maps
affected_visualizations.select { |v| v.has_read_permission?(user) && v.derived? ? v : nil }
end
def partially_dependent_visualizations
affected_visualizations.select { |v| v.partially_dependent_on?(self) }
end
def dependent_visualizations
affected_visualizations.select { |v| v.dependent_on?(self) }
end
def faster_dependent_visualizations(limit: nil)
query = %{
SELECT * FROM (
SELECT DISTINCT ON (visualizations.id) *
FROM layers_user_tables, layers_maps, visualizations
WHERE layers_user_tables.user_table_id = '#{id}'
AND layers_user_tables.layer_id = layers_maps.layer_id
AND layers_maps.map_id = visualizations.map_id
AND visualizations.type = 'derived'
) v
ORDER BY v.updated_at DESC
}
query = query + " LIMIT(#{limit})" if limit
Carto::Visualization.find_by_sql(query)
end
def dependent_visualizations_count
query = %{
SELECT count(distinct(visualizations.id))
FROM layers_user_tables, layers_maps, visualizations
WHERE layers_user_tables.user_table_id = '#{id}'
AND layers_user_tables.layer_id = layers_maps.layer_id
AND layers_maps.map_id = visualizations.map_id
AND visualizations.type = 'derived'
}
result = ActiveRecord::Base.connection.execute(query)
result.first['count'].to_i
end
def affected_visualizations
layers.map(&:visualization).uniq.compact
end
def name_for_user(other_user)
is_owner?(other_user) ? name : fully_qualified_name
end
def private?
privacy == PRIVACY_PRIVATE
end
def public?
privacy == PRIVACY_PUBLIC
end
def public_with_link_only?
privacy == PRIVACY_LINK
end
def privacy_text
visualization_privacy.upcase
end
def visualization_privacy
PRIVACY_VALUES_TO_TEXTS[privacy]
end
def readable_by?(user)
!private? || is_owner?(user) || visualization_readable_by?(user)
end
def raster?
service.is_raster?
end
def geometry_type
service.the_geom_type || 'geometry'
end
def estimated_row_count
service.estimated_row_count
end
def actual_row_count
service.actual_row_count
end
def sync_table_id
self.table_id = service.get_table_id
end
def permission
visualization.permission if visualization
end
def external_source_visualization
data_import.try(:external_data_imports).try(:first).try(:external_source).try(:visualization)
end
def table_visualization
map.visualization if map
end
def update_cdb_tablemetadata
service.update_cdb_tablemetadata
end
def save_changes
# TODO: Compatibility with Sequel model, can be removed afterwards. Used in ::Table.set_the_geom_column!
save if changed?
end
def tags=(value)
return unless value
super(value.split(',').map(&:strip).reject(&:blank?).uniq.join(','))
end
# TODO: Compatibility with Sequel model, can be removed afterwards.
def set_tag_array(tag_array)
self.tags = tag_array.join(',')
end
# TODO: This is related to an incompatibility between visualizations models, `get_related_tables`, See #11705
def privacy_text_for_vizjson
privacy == PRIVACY_LINK ? 'PUBLIC' : privacy_text
end
def is_owner?(user)
return false unless user
user_id == user.id
end
private
def default_privacy_value
user.try(:default_table_privacy)
end
def set_default_table_privacy
self.privacy ||= default_privacy_value
end
def fully_qualified_name
"\"#{user.database_schema}\".#{name}"
end
def visualization_readable_by?(user)
user && permission && permission.user_has_read_permission?(user)
end
def validate_user_not_viewer
errors.add(:user, "Viewer users can't create tables") if user.try(:viewer)
end
def validate_privacy_changes
if !user.try(:private_tables_enabled) && !public? && (new_record? || privacy_changed?)
errors.add(:privacy, 'unauthorized to create private tables')
end
if public? && (new_record? || privacy_changed?) && CartoDB::QuotaChecker.new(user).will_be_over_public_dataset_quota?
errors.add(:privacy, 'unauthorized to create public tables')
end
end
def create_canonical_visualization
visualization = Carto::VisualizationFactory.create_canonical_visualization(self)
update_attribute(:map, visualization.map)
visualization.map.set_default_boundaries!
end
def cache_dependent_visualizations
@fully_dependent_visualizations_cache = fully_dependent_visualizations
@partially_dependent_visualizations_cache = partially_dependent_visualizations
end
def destroy_dependent_visualizations
table_visualization.try(:delete_from_table)
@fully_dependent_visualizations_cache.each(&:destroy)
@partially_dependent_visualizations_cache.each do |visualization|
visualization.unlink_from(self)
end
end
def ensure_not_viewer
# Loading ::User is a workaround for User deletion: viewer attribute change is not visible at AR transaction
raise "Viewer users can't destroy tables" if user && user.viewer && ::User[user_id].viewer
end
def service_before_create
service.before_create
end
def service_after_create
service.after_create
end
def service_after_save
service.after_save
end
def service_after_destroy
service.after_destroy
end
end
end