diff --git a/db/docs/views/postgres_sequences.yml b/db/docs/views/postgres_sequences.yml new file mode 100644 index 0000000000000000000000000000000000000000..7937a5511bf3a361e7db1c05b547e9269e63d5e8 --- /dev/null +++ b/db/docs/views/postgres_sequences.yml @@ -0,0 +1,10 @@ +--- +view_name: postgres_sequences +classes: + - Gitlab::Database::PostgresSequence +feature_categories: + - database +description: SQL view to get information about postgres sequences +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/139117 +milestone: '16.7' +gitlab_schema: gitlab_shared diff --git a/db/migrate/20231207144215_add_postgres_sequences_view.rb b/db/migrate/20231207144215_add_postgres_sequences_view.rb new file mode 100644 index 0000000000000000000000000000000000000000..6187bb5f15ee86f946de415909f7e17c943e904a --- /dev/null +++ b/db/migrate/20231207144215_add_postgres_sequences_view.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +class AddPostgresSequencesView < Gitlab::Database::Migration[2.2] + milestone '16.7' + enable_lock_retries! + + def up + execute(<<~SQL) + CREATE OR REPLACE VIEW postgres_sequences + AS + SELECT seq_pg_class.relname AS seq_name, + dep_pg_class.relname AS table_name, + pg_attribute.attname AS col_name + FROM pg_class seq_pg_class + INNER JOIN pg_depend ON seq_pg_class.oid = pg_depend.objid + INNER JOIN pg_class dep_pg_class ON pg_depend.refobjid = dep_pg_class.oid + INNER JOIN pg_attribute ON dep_pg_class.oid = pg_attribute.attrelid + AND pg_depend.refobjsubid = pg_attribute.attnum + WHERE seq_pg_class.relkind = 'S' + SQL + end + + def down + execute(<<~SQL) + DROP VIEW postgres_sequences; + SQL + end +end diff --git a/db/schema_migrations/20231207144215 b/db/schema_migrations/20231207144215 new file mode 100644 index 0000000000000000000000000000000000000000..971d491857792817b9a101221d1c2c812be38fd6 --- /dev/null +++ b/db/schema_migrations/20231207144215 @@ -0,0 +1 @@ +871cc15f04f235ff2719eb334c28041a0f1093653e5ca2fad5e92b911622d221 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 3e55c8ff61552bbf7513ad0ead4c8cd4b9f9744b..3256c4b0e079ee08d1f03a8c3e5207e7e0130ce3 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -21617,6 +21617,16 @@ CREATE SEQUENCE postgres_reindex_queued_actions_id_seq ALTER SEQUENCE postgres_reindex_queued_actions_id_seq OWNED BY postgres_reindex_queued_actions.id; +CREATE VIEW postgres_sequences AS + SELECT seq_pg_class.relname AS seq_name, + dep_pg_class.relname AS table_name, + pg_attribute.attname AS col_name + FROM (((pg_class seq_pg_class + JOIN pg_depend ON ((seq_pg_class.oid = pg_depend.objid))) + JOIN pg_class dep_pg_class ON ((pg_depend.refobjid = dep_pg_class.oid))) + JOIN pg_attribute ON (((dep_pg_class.oid = pg_attribute.attrelid) AND (pg_depend.refobjsubid = pg_attribute.attnum)))) + WHERE (seq_pg_class.relkind = 'S'::"char"); + CREATE TABLE programming_languages ( id integer NOT NULL, name character varying NOT NULL, diff --git a/lib/gitlab/database/postgres_sequence.rb b/lib/gitlab/database/postgres_sequence.rb new file mode 100644 index 0000000000000000000000000000000000000000..bf394d80e12ddd9851d45f6009d1e63f951a84dd --- /dev/null +++ b/lib/gitlab/database/postgres_sequence.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module Gitlab + module Database + # Backed by the postgres_sequences view + class PostgresSequence < SharedModel + self.primary_key = :seq_name + + scope :by_table_name, ->(table_name) { where(table_name: table_name) } + end + end +end diff --git a/spec/lib/gitlab/database/postgres_sequences_spec.rb b/spec/lib/gitlab/database/postgres_sequences_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..2373edaea18e36d9afb35358b5b6e7af58d41183 --- /dev/null +++ b/spec/lib/gitlab/database/postgres_sequences_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Database::PostgresSequence, type: :model, feature_category: :database do + # PostgresSequence does not `behaves_like 'a postgres model'` because it does not correspond 1-1 with a single entry + # in pg_class + let(:schema) { ActiveRecord::Base.connection.current_schema } + let(:table_name) { '_test_table' } + let(:table_name_without_sequence) { '_test_table_without_sequence' } + + before do + ActiveRecord::Base.connection.execute(<<~SQL) + CREATE TABLE #{table_name} ( + id bigserial PRIMARY KEY NOT NULL + ); + + CREATE TABLE #{table_name_without_sequence} ( + id bigint PRIMARY KEY NOT NULL + ); + SQL + end + + describe '#by_table_name' do + context 'when table does not have a sequence' do + it 'returns an empty collection' do + expect(described_class.by_table_name(table_name_without_sequence)).to be_empty + end + end + + it 'returns the sequence for a given table' do + expect(described_class.by_table_name(table_name).first[:table_name]).to eq(table_name) + end + end +end