Add packages_rubygems_spec_files table, model, and uploader
Summary
This is the first of three MRs to implement RubyGems spec index file generation for the package registry ([related issue link here]).
- MR 1 (this one): Database table, model, uploader, and specs
- MR 2 (pending this merge):
CreateSpecFilesService,CreateSpecFilesWorker, and hooks intoProcessGemService/MarkPackageForDestructionService - MR 3 (pending MR 2): API endpoint to serve spec index files, request specs, and docs
What does this MR do?
Introduces the packages_rubygems_spec_files table and associated Packages::Rubygems::SpecFile model to store generated RubyGems spec index files (specs.4.8.gz, latest_specs.4.8.gz, prerelease_specs.4.8.gz) per project. This follows the same pattern as Packages::Npm::MetadataCache and Packages::Helm::MetadataCache.
Changes:
- New migration creating
packages_rubygems_spec_fileswith a unique index on(project_id, file_name)and a unique index onobject_storage_key Packages::Rubygems::SpecFilemodel withFileStoreMounter,ObjectStorable, andPackages::DestructibleconcernsPackages::Rubygems::SpecFileUploader(extendsPackages::BaseMetadataCacheUploader)has_many :rubygems_spec_filesassociation onProject- Factory, fixture, and model specs
Database
New table: packages_rubygems_spec_files
| Column | Type | Constraints |
|---|---|---|
id |
bigint |
PK |
created_at |
timestamptz |
NOT NULL |
updated_at |
timestamptz |
NOT NULL |
project_id |
bigint |
NOT NULL |
size |
integer |
NOT NULL |
status |
smallint |
NOT NULL, DEFAULT 0 |
file_store |
integer |
DEFAULT 1 |
file_name |
text |
NOT NULL, limit 255 |
file |
text |
NOT NULL, limit 255 |
object_storage_key |
text |
NOT NULL, limit 255 |
Indexes
index_packages_rubygems_spec_files_on_project_id_and_file_name— UNIQUE on(project_id, file_name)index_packages_rubygems_spec_files_on_object_storage_key— UNIQUE on(object_storage_key)
Query Plans
All queries use the unique index on `(project_id, file_name)`.
**1. Lookup by project and file name** (used by `find_or_build` and `with_project_and_file_name` scope):
```sql
SELECT * FROM packages_rubygems_spec_files
WHERE project_id = 1 AND file_name = 'specs.4.8.gz'
LIMIT 1;
- Time: 0.509 ms (planning: 0.449 ms, execution: 0.060 ms)
- Plan: Index Scan using
index_packages_rubygems_spec_files_on_project_id_and_file_name - Full plan
2. Insert new spec file:
INSERT INTO packages_rubygems_spec_files
(project_id, file_name, file, size, status, object_storage_key, created_at, updated_at)
VALUES (1, 'specs.4.8.gz', 'spec_file.gz', 1024, 0, 'packages/rubygems/spec_files/abc123', now(), now());
- Time: 2.868 ms (planning: 0.081 ms, execution: 2.787 ms)
- Full plan
3. Update existing spec file:
UPDATE packages_rubygems_spec_files
SET file = 'spec_file.gz', size = 2048, updated_at = now()
WHERE project_id = 1 AND file_name = 'specs.4.8.gz';
- Time: 0.514 ms (planning: 0.395 ms, execution: 0.119 ms)
- Plan: Index Scan using
index_packages_rubygems_spec_files_on_project_id_and_file_name - Full plan
MR acceptance checklist
db/structure.sqlupdated (pending — requiresbundle exec rake db:migrate db:schema:dump)- Database dictionary file included (
db/docs/packages_rubygems_spec_files.yml) - Model specs passing
- Followsdatabase review guidelines
- Database dictionary file included (