half-orm-dev 0.16.0a9__py3-none-any.whl

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 (58) hide show
  1. half_orm_dev/__init__.py +1 -0
  2. half_orm_dev/cli/__init__.py +9 -0
  3. half_orm_dev/cli/commands/__init__.py +56 -0
  4. half_orm_dev/cli/commands/apply.py +13 -0
  5. half_orm_dev/cli/commands/clone.py +102 -0
  6. half_orm_dev/cli/commands/init.py +331 -0
  7. half_orm_dev/cli/commands/new.py +15 -0
  8. half_orm_dev/cli/commands/patch.py +317 -0
  9. half_orm_dev/cli/commands/prepare.py +21 -0
  10. half_orm_dev/cli/commands/prepare_release.py +119 -0
  11. half_orm_dev/cli/commands/promote_to.py +127 -0
  12. half_orm_dev/cli/commands/release.py +344 -0
  13. half_orm_dev/cli/commands/restore.py +14 -0
  14. half_orm_dev/cli/commands/sync.py +13 -0
  15. half_orm_dev/cli/commands/todo.py +73 -0
  16. half_orm_dev/cli/commands/undo.py +17 -0
  17. half_orm_dev/cli/commands/update.py +73 -0
  18. half_orm_dev/cli/commands/upgrade.py +191 -0
  19. half_orm_dev/cli/main.py +103 -0
  20. half_orm_dev/cli_extension.py +38 -0
  21. half_orm_dev/database.py +1389 -0
  22. half_orm_dev/hgit.py +1025 -0
  23. half_orm_dev/hop.py +167 -0
  24. half_orm_dev/manifest.py +43 -0
  25. half_orm_dev/modules.py +456 -0
  26. half_orm_dev/patch.py +281 -0
  27. half_orm_dev/patch_manager.py +1694 -0
  28. half_orm_dev/patch_validator.py +335 -0
  29. half_orm_dev/patches/0/1/0/00_half_orm_meta.database.sql +34 -0
  30. half_orm_dev/patches/0/1/0/01_alter_half_orm_meta.hop_release.sql +2 -0
  31. half_orm_dev/patches/0/1/0/02_half_orm_meta.view.hop_penultimate_release.sql +3 -0
  32. half_orm_dev/patches/log +2 -0
  33. half_orm_dev/patches/sql/half_orm_meta.sql +208 -0
  34. half_orm_dev/release_manager.py +2841 -0
  35. half_orm_dev/repo.py +1562 -0
  36. half_orm_dev/templates/.gitignore +15 -0
  37. half_orm_dev/templates/MANIFEST.in +1 -0
  38. half_orm_dev/templates/Pipfile +13 -0
  39. half_orm_dev/templates/README +25 -0
  40. half_orm_dev/templates/conftest_template +42 -0
  41. half_orm_dev/templates/init_module_template +10 -0
  42. half_orm_dev/templates/module_template_1 +12 -0
  43. half_orm_dev/templates/module_template_2 +6 -0
  44. half_orm_dev/templates/module_template_3 +3 -0
  45. half_orm_dev/templates/relation_test +23 -0
  46. half_orm_dev/templates/setup.py +81 -0
  47. half_orm_dev/templates/sql_adapter +9 -0
  48. half_orm_dev/templates/warning +12 -0
  49. half_orm_dev/utils.py +49 -0
  50. half_orm_dev/version.txt +1 -0
  51. half_orm_dev-0.16.0a9.dist-info/METADATA +935 -0
  52. half_orm_dev-0.16.0a9.dist-info/RECORD +58 -0
  53. half_orm_dev-0.16.0a9.dist-info/WHEEL +5 -0
  54. half_orm_dev-0.16.0a9.dist-info/licenses/AUTHORS +3 -0
  55. half_orm_dev-0.16.0a9.dist-info/licenses/LICENSE +14 -0
  56. half_orm_dev-0.16.0a9.dist-info/top_level.txt +2 -0
  57. tests/__init__.py +0 -0
  58. tests/conftest.py +329 -0
@@ -0,0 +1,335 @@
1
+ """
2
+ Patch ID validation and normalization for half-orm-dev.
3
+
4
+ This module provides validation and normalization of patch identifiers
5
+ used in the patch-centric workflow.
6
+ """
7
+
8
+ import re
9
+ import unicodedata
10
+ from typing import Optional
11
+ from dataclasses import dataclass
12
+
13
+
14
+ class InvalidPatchIdError(Exception):
15
+ """Raised when patch ID format is invalid."""
16
+ pass
17
+
18
+
19
+ class DuplicatePatchIdError(Exception):
20
+ """Raised when patch ID already exists."""
21
+ pass
22
+
23
+
24
+ @dataclass
25
+ class PatchInfo:
26
+ """Information about a validated patch ID."""
27
+ original_id: str
28
+ normalized_id: str
29
+ ticket_number: Optional[str]
30
+ description: Optional[str]
31
+ is_numeric_only: bool
32
+
33
+
34
+ class PatchValidator:
35
+ """
36
+ Validates and normalizes patch IDs for the patch-centric workflow.
37
+
38
+ Handles both formats:
39
+ - Numeric only: "456" -> generates description if possible
40
+ - Full format: "456-user-authentication" -> validates format
41
+
42
+ Examples:
43
+ validator = PatchValidator()
44
+
45
+ # Numeric patch ID
46
+ info = validator.validate_patch_id("456")
47
+ # Returns: PatchInfo(original_id="456", normalized_id="456", ...)
48
+
49
+ # Full patch ID
50
+ info = validator.validate_patch_id("456-user-authentication")
51
+ # Returns: PatchInfo(original_id="456-user-authentication",
52
+ # normalized_id="456-user-authentication", ...)
53
+
54
+ # Invalid format raises exception
55
+ try:
56
+ validator.validate_patch_id("invalid@patch")
57
+ except InvalidPatchIdError as e:
58
+ print(f"Invalid patch ID: {e}")
59
+ """
60
+
61
+ # Regex patterns for validation
62
+ NUMERIC_PATTERN = re.compile(r'^\d+$')
63
+ FULL_PATTERN = re.compile(r'^\d+-[a-z0-9]+(?:-[a-z0-9]+)*$')
64
+ DESCRIPTION_PATTERN = re.compile(r'^[a-z0-9]+(?:-[a-z0-9]+)*$')
65
+
66
+ def __init__(self):
67
+ """Initialize patch validator."""
68
+ pass
69
+
70
+ def validate_patch_id(self, patch_id: str) -> PatchInfo:
71
+ """
72
+ Validate and parse a patch ID.
73
+
74
+ Args:
75
+ patch_id: The patch identifier to validate
76
+
77
+ Returns:
78
+ PatchInfo object with parsed information
79
+
80
+ Raises:
81
+ InvalidPatchIdError: If patch ID format is invalid
82
+
83
+ Examples:
84
+ # Numeric ID
85
+ info = validator.validate_patch_id("456")
86
+ assert info.ticket_number == "456"
87
+ assert info.is_numeric_only == True
88
+
89
+ # Full ID
90
+ info = validator.validate_patch_id("456-user-auth")
91
+ assert info.ticket_number == "456"
92
+ assert info.description == "user-auth"
93
+ assert info.is_numeric_only == False
94
+ """
95
+ if not patch_id or not patch_id.strip():
96
+ raise InvalidPatchIdError("Patch ID cannot be empty")
97
+
98
+ patch_id = patch_id.strip()
99
+
100
+ # Check for numeric-only format
101
+ if self.NUMERIC_PATTERN.match(patch_id):
102
+ return PatchInfo(
103
+ original_id=patch_id,
104
+ normalized_id=patch_id,
105
+ ticket_number=patch_id,
106
+ description=None,
107
+ is_numeric_only=True
108
+ )
109
+
110
+ # Check for full format (number-description)
111
+ if self.FULL_PATTERN.match(patch_id):
112
+ parts = patch_id.split('-', 1)
113
+ ticket_number = parts[0]
114
+ description = parts[1]
115
+
116
+ return PatchInfo(
117
+ original_id=patch_id,
118
+ normalized_id=patch_id,
119
+ ticket_number=ticket_number,
120
+ description=description,
121
+ is_numeric_only=False
122
+ )
123
+
124
+ # If we get here, format is invalid
125
+ if not patch_id[0].isdigit():
126
+ raise InvalidPatchIdError("Patch ID must start with a ticket number")
127
+ else:
128
+ raise InvalidPatchIdError(
129
+ f"Invalid patch ID format: '{patch_id}'. "
130
+ f"Expected formats: '123' or '123-description' (lowercase, hyphens only)"
131
+ )
132
+
133
+ def normalize_patch_id(self, patch_id: str, suggested_description: Optional[str] = None) -> str:
134
+ """
135
+ Normalize a patch ID to the standard format.
136
+
137
+ For numeric IDs, tries to generate a meaningful description.
138
+ For full IDs, validates format and returns as-is.
139
+
140
+ Args:
141
+ patch_id: The patch identifier to normalize
142
+ suggested_description: Optional description to use for numeric IDs
143
+
144
+ Returns:
145
+ Normalized patch ID in format "number-description"
146
+
147
+ Raises:
148
+ InvalidPatchIdError: If patch ID format is invalid
149
+
150
+ Examples:
151
+ # Numeric with suggestion
152
+ result = validator.normalize_patch_id("456", "user-authentication")
153
+ assert result == "456-user-authentication"
154
+
155
+ # Numeric without suggestion (uses fallback)
156
+ result = validator.normalize_patch_id("456")
157
+ assert result == "456" # or "456-feature" based on context
158
+
159
+ # Already normalized
160
+ result = validator.normalize_patch_id("456-existing")
161
+ assert result == "456-existing"
162
+ """
163
+ # First validate the input format
164
+ patch_info = self.validate_patch_id(patch_id)
165
+
166
+ # If it's already in full format, return as-is
167
+ if not patch_info.is_numeric_only:
168
+ return patch_info.normalized_id
169
+
170
+ # For numeric-only IDs, we need to add a description
171
+ if suggested_description:
172
+ # Sanitize the suggested description
173
+ clean_description = self.sanitize_description(suggested_description)
174
+ return f"{patch_info.ticket_number}-{clean_description}"
175
+ else:
176
+ # Use fallback description
177
+ fallback_description = self.generate_fallback_description(patch_info.ticket_number)
178
+ return f"{patch_info.ticket_number}-{fallback_description}"
179
+
180
+ def extract_ticket_number(self, patch_id: str) -> Optional[str]:
181
+ """
182
+ Extract ticket number from patch ID.
183
+
184
+ Args:
185
+ patch_id: The patch identifier
186
+
187
+ Returns:
188
+ Ticket number if found, None otherwise
189
+
190
+ Examples:
191
+ assert validator.extract_ticket_number("456-auth") == "456"
192
+ assert validator.extract_ticket_number("456") == "456"
193
+ assert validator.extract_ticket_number("invalid") is None
194
+ """
195
+ if not patch_id or not patch_id.strip():
196
+ return None
197
+
198
+ patch_id = patch_id.strip()
199
+
200
+ # Check numeric-only format
201
+ if self.NUMERIC_PATTERN.match(patch_id):
202
+ return patch_id
203
+
204
+ # Check full format and extract number part
205
+ if self.FULL_PATTERN.match(patch_id):
206
+ return patch_id.split('-', 1)[0]
207
+
208
+ # Invalid format
209
+ return None
210
+
211
+ def extract_description(self, patch_id: str) -> Optional[str]:
212
+ """
213
+ Extract description part from patch ID.
214
+
215
+ Args:
216
+ patch_id: The patch identifier
217
+
218
+ Returns:
219
+ Description if found, None for numeric-only IDs
220
+
221
+ Examples:
222
+ assert validator.extract_description("456-user-auth") == "user-auth"
223
+ assert validator.extract_description("456") is None
224
+ """
225
+ if not patch_id or not patch_id.strip():
226
+ return None
227
+
228
+ patch_id = patch_id.strip()
229
+
230
+ # Numeric-only format has no description
231
+ if self.NUMERIC_PATTERN.match(patch_id):
232
+ return None
233
+
234
+ # Extract description from full format
235
+ if self.FULL_PATTERN.match(patch_id):
236
+ parts = patch_id.split('-', 1)
237
+ return parts[1] # Return everything after first hyphen
238
+
239
+ # Invalid format
240
+ return None
241
+
242
+ def is_valid_description(self, description: str) -> bool:
243
+ """
244
+ Check if description part follows naming conventions.
245
+
246
+ Args:
247
+ description: Description to validate
248
+
249
+ Returns:
250
+ True if description is valid, False otherwise
251
+
252
+ Examples:
253
+ assert validator.is_valid_description("user-authentication") == True
254
+ assert validator.is_valid_description("user_auth") == False # no underscores
255
+ assert validator.is_valid_description("UserAuth") == False # no uppercase
256
+ """
257
+ if not description:
258
+ return False
259
+
260
+ # Use the DESCRIPTION_PATTERN regex to validate format
261
+ return bool(self.DESCRIPTION_PATTERN.match(description))
262
+
263
+ def generate_fallback_description(self, ticket_number: str) -> str:
264
+ """
265
+ Generate a fallback description for numeric patch IDs.
266
+
267
+ Returns a simple default description. Developers should provide
268
+ meaningful descriptions themselves for better clarity.
269
+
270
+ Args:
271
+ ticket_number: The numeric ticket identifier
272
+
273
+ Returns:
274
+ Default description "patch"
275
+
276
+ Examples:
277
+ desc = validator.generate_fallback_description("456")
278
+ assert desc == "patch"
279
+ """
280
+ return "patch"
281
+
282
+ def sanitize_description(self, description: str) -> str:
283
+ """
284
+ Sanitize a description to follow naming conventions.
285
+
286
+ - Convert to lowercase
287
+ - Replace spaces/underscores with hyphens
288
+ - Remove invalid characters
289
+ - Truncate if too long
290
+
291
+ Args:
292
+ description: Raw description to sanitize
293
+
294
+ Returns:
295
+ Sanitized description following conventions
296
+
297
+ Examples:
298
+ assert validator.sanitize_description("User Authentication") == "user-authentication"
299
+ assert validator.sanitize_description("user_auth_system") == "user-auth-system"
300
+ assert validator.sanitize_description("Fix Bug #123") == "fix-bug-123"
301
+ """
302
+ if not description:
303
+ return "patch"
304
+
305
+ # Convert to lowercase
306
+ result = description.lower()
307
+
308
+ # Remove accents (transliteration)
309
+ result = unicodedata.normalize('NFD', result)
310
+ result = ''.join(c for c in result if unicodedata.category(c) != 'Mn')
311
+
312
+ # Replace spaces and underscores with hyphens
313
+ result = re.sub(r'[\s_]+', '-', result)
314
+
315
+ # Replace other separators (dots, @, etc.) with hyphens before removing them
316
+ result = re.sub(r'[^\w\-]', '-', result)
317
+
318
+ # Now remove invalid characters (keep only letters, numbers, hyphens)
319
+ result = re.sub(r'[^a-z0-9\-]', '', result)
320
+
321
+ # Clean up multiple consecutive hyphens
322
+ result = re.sub(r'-+', '-', result)
323
+
324
+ # Remove leading and trailing hyphens
325
+ result = result.strip('-')
326
+
327
+ # If result is empty after cleaning, use fallback
328
+ if not result:
329
+ return "patch"
330
+
331
+ # Truncate if too long (reasonable limit)
332
+ if len(result) > 50:
333
+ result = result[:50].rstrip('-')
334
+
335
+ return result
@@ -0,0 +1,34 @@
1
+ create extension if not exists pgcrypto;
2
+
3
+ create table if not exists half_orm_meta.database (
4
+ id text primary key,
5
+ name text not null,
6
+ description text
7
+ );
8
+
9
+
10
+ create or replace function half_orm_meta.check_database(old_dbid text default null) returns text as $$
11
+ DECLARE
12
+ dbname text;
13
+ dbid text;
14
+ BEGIN
15
+ select current_database() into dbname;
16
+ --XXX: use a materialized view.
17
+ BEGIN
18
+ select encode(hmac(dbname, pg_read_file('hop_key'), 'sha1'), 'hex') into dbid;
19
+ EXCEPTION
20
+ when undefined_file then
21
+ raise NOTICE 'No hop_key file for the cluster. Will use % for dbid', dbname;
22
+ dbid := dbname;
23
+ END;
24
+ if old_dbid is not null and old_dbid != dbid
25
+ then
26
+ raise Exception 'Not the same database!';
27
+ end if;
28
+ return dbid;
29
+ END;
30
+ $$ language plpgsql;
31
+
32
+ insert into half_orm_meta.database (id, name) values ((select half_orm_meta.check_database()), (select current_database()));
33
+
34
+ comment on table half_orm_meta.database is 'id identifies the database in the cluster. It uses the key in hop_key.';
@@ -0,0 +1,2 @@
1
+ alter table half_orm_meta.hop_release add column dbid text references half_orm_meta.database(id) on update cascade;
2
+ alter table half_orm_meta.hop_release add column hop_release text;
@@ -0,0 +1,3 @@
1
+ drop view if exists "half_orm_meta.view".hop_penultimate_release;
2
+ create view "half_orm_meta.view".hop_penultimate_release as
3
+ select * from (select major, minor, patch from half_orm_meta.hop_release order by major desc, minor desc, patch desc limit 2 ) as penultimate order by major, minor, patch limit 1;
@@ -0,0 +1,2 @@
1
+ 0.0.26 t581fd33
2
+ 0.1.0
@@ -0,0 +1,208 @@
1
+ --
2
+ -- PostgreSQL database dump
3
+ --
4
+
5
+ -- Dumped from database version 13.11 (Debian 13.11-1.pgdg110+1)
6
+ -- Dumped by pg_dump version 13.11 (Debian 13.11-1.pgdg110+1)
7
+
8
+ SET statement_timeout = 0;
9
+ SET lock_timeout = 0;
10
+ SET idle_in_transaction_session_timeout = 0;
11
+ SET client_encoding = 'UTF8';
12
+ SET standard_conforming_strings = on;
13
+ SELECT pg_catalog.set_config('search_path', '', false);
14
+ SET check_function_bodies = false;
15
+ SET xmloption = content;
16
+ SET client_min_messages = warning;
17
+ SET row_security = off;
18
+
19
+ --
20
+ -- Name: half_orm_meta; Type: SCHEMA; Schema: -; Owner: -
21
+ --
22
+
23
+ CREATE SCHEMA half_orm_meta;
24
+
25
+
26
+ --
27
+ -- Name: half_orm_meta.view; Type: SCHEMA; Schema: -; Owner: -
28
+ --
29
+
30
+ CREATE SCHEMA "half_orm_meta.view";
31
+
32
+
33
+ --
34
+ -- Name: check_database(text); Type: FUNCTION; Schema: half_orm_meta; Owner: -
35
+ --
36
+
37
+ CREATE FUNCTION half_orm_meta.check_database(old_dbid text DEFAULT NULL::text) RETURNS text
38
+ LANGUAGE plpgsql
39
+ AS $$
40
+ DECLARE
41
+ dbname text;
42
+ dbid text;
43
+ BEGIN
44
+ select current_database() into dbname;
45
+ --XXX: use a materialized view.
46
+ BEGIN
47
+ select encode(hmac(dbname, pg_read_file('hop_key'), 'sha1'), 'hex') into dbid;
48
+ EXCEPTION
49
+ when undefined_file then
50
+ raise NOTICE 'No hop_key file for the cluster. Will use % for dbid', dbname;
51
+ dbid := dbname;
52
+ END;
53
+ if old_dbid is not null and old_dbid != dbid
54
+ then
55
+ raise Exception 'Not the same database!';
56
+ end if;
57
+ return dbid;
58
+ END;
59
+ $$;
60
+
61
+
62
+ SET default_tablespace = '';
63
+
64
+ SET default_table_access_method = heap;
65
+
66
+ --
67
+ -- Name: database; Type: TABLE; Schema: half_orm_meta; Owner: -
68
+ --
69
+
70
+ CREATE TABLE half_orm_meta.database (
71
+ id text NOT NULL,
72
+ name text NOT NULL,
73
+ description text
74
+ );
75
+
76
+
77
+ --
78
+ -- Name: TABLE database; Type: COMMENT; Schema: half_orm_meta; Owner: -
79
+ --
80
+
81
+ COMMENT ON TABLE half_orm_meta.database IS '
82
+ id identifies the database in the cluster. It uses the key
83
+ in hop_key.
84
+ ';
85
+
86
+
87
+ --
88
+ -- Name: hop_release; Type: TABLE; Schema: half_orm_meta; Owner: -
89
+ --
90
+
91
+ CREATE TABLE half_orm_meta.hop_release (
92
+ major integer NOT NULL,
93
+ minor integer NOT NULL,
94
+ patch integer NOT NULL,
95
+ pre_release text DEFAULT ''::text NOT NULL,
96
+ pre_release_num text DEFAULT ''::text NOT NULL,
97
+ date date DEFAULT CURRENT_DATE,
98
+ "time" time(0) with time zone DEFAULT CURRENT_TIME,
99
+ changelog text,
100
+ commit text,
101
+ dbid text,
102
+ hop_release text,
103
+ CONSTRAINT hop_release_major_check CHECK ((major >= 0)),
104
+ CONSTRAINT hop_release_minor_check CHECK ((minor >= 0)),
105
+ CONSTRAINT hop_release_patch_check CHECK ((patch >= 0)),
106
+ CONSTRAINT hop_release_pre_release_check CHECK ((pre_release = ANY (ARRAY['alpha'::text, 'beta'::text, 'rc'::text, ''::text]))),
107
+ CONSTRAINT hop_release_pre_release_num_check CHECK (((pre_release_num = ''::text) OR (pre_release_num ~ '^\d+$'::text)))
108
+ );
109
+
110
+
111
+ --
112
+ -- Name: hop_release_issue; Type: TABLE; Schema: half_orm_meta; Owner: -
113
+ --
114
+
115
+ CREATE TABLE half_orm_meta.hop_release_issue (
116
+ num integer NOT NULL,
117
+ issue_release integer DEFAULT 0 NOT NULL,
118
+ release_major integer NOT NULL,
119
+ release_minor integer NOT NULL,
120
+ release_patch integer NOT NULL,
121
+ release_pre_release text NOT NULL,
122
+ release_pre_release_num text NOT NULL,
123
+ changelog text,
124
+ CONSTRAINT hop_release_issue_num_check CHECK ((num >= 0))
125
+ );
126
+
127
+
128
+ --
129
+ -- Name: hop_last_release; Type: VIEW; Schema: half_orm_meta.view; Owner: -
130
+ --
131
+
132
+ CREATE VIEW "half_orm_meta.view".hop_last_release AS
133
+ SELECT hop_release.major,
134
+ hop_release.minor,
135
+ hop_release.patch,
136
+ hop_release.pre_release,
137
+ hop_release.pre_release_num,
138
+ hop_release.date,
139
+ hop_release."time",
140
+ hop_release.changelog,
141
+ hop_release.commit
142
+ FROM half_orm_meta.hop_release
143
+ ORDER BY hop_release.major DESC, hop_release.minor DESC, hop_release.patch DESC, hop_release.pre_release DESC, hop_release.pre_release_num DESC
144
+ LIMIT 1;
145
+
146
+
147
+ --
148
+ -- Name: hop_penultimate_release; Type: VIEW; Schema: half_orm_meta.view; Owner: -
149
+ --
150
+
151
+ CREATE VIEW "half_orm_meta.view".hop_penultimate_release AS
152
+ SELECT penultimate.major,
153
+ penultimate.minor,
154
+ penultimate.patch
155
+ FROM ( SELECT hop_release.major,
156
+ hop_release.minor,
157
+ hop_release.patch
158
+ FROM half_orm_meta.hop_release
159
+ ORDER BY hop_release.major DESC, hop_release.minor DESC, hop_release.patch DESC
160
+ LIMIT 2) penultimate
161
+ ORDER BY penultimate.major, penultimate.minor, penultimate.patch
162
+ LIMIT 1;
163
+
164
+
165
+ --
166
+ -- Name: database database_pkey; Type: CONSTRAINT; Schema: half_orm_meta; Owner: -
167
+ --
168
+
169
+ ALTER TABLE ONLY half_orm_meta.database
170
+ ADD CONSTRAINT database_pkey PRIMARY KEY (id);
171
+
172
+
173
+ --
174
+ -- Name: hop_release_issue hop_release_issue_pkey; Type: CONSTRAINT; Schema: half_orm_meta; Owner: -
175
+ --
176
+
177
+ ALTER TABLE ONLY half_orm_meta.hop_release_issue
178
+ ADD CONSTRAINT hop_release_issue_pkey PRIMARY KEY (num, issue_release);
179
+
180
+
181
+ --
182
+ -- Name: hop_release hop_release_pkey; Type: CONSTRAINT; Schema: half_orm_meta; Owner: -
183
+ --
184
+
185
+ ALTER TABLE ONLY half_orm_meta.hop_release
186
+ ADD CONSTRAINT hop_release_pkey PRIMARY KEY (major, minor, patch, pre_release, pre_release_num);
187
+
188
+
189
+ --
190
+ -- Name: hop_release hop_release_dbid_fkey; Type: FK CONSTRAINT; Schema: half_orm_meta; Owner: -
191
+ --
192
+
193
+ ALTER TABLE ONLY half_orm_meta.hop_release
194
+ ADD CONSTRAINT hop_release_dbid_fkey FOREIGN KEY (dbid) REFERENCES half_orm_meta.database(id) ON UPDATE CASCADE;
195
+
196
+
197
+ --
198
+ -- Name: hop_release_issue hop_release_issue_release_major_release_minor_release_patc_fkey; Type: FK CONSTRAINT; Schema: half_orm_meta; Owner: -
199
+ --
200
+
201
+ ALTER TABLE ONLY half_orm_meta.hop_release_issue
202
+ ADD CONSTRAINT hop_release_issue_release_major_release_minor_release_patc_fkey FOREIGN KEY (release_major, release_minor, release_patch, release_pre_release, release_pre_release_num) REFERENCES half_orm_meta.hop_release(major, minor, patch, pre_release, pre_release_num);
203
+
204
+
205
+ --
206
+ -- PostgreSQL database dump complete
207
+ --
208
+