1use std::collections::HashMap;
4
5use chrono::Utc;
6use plsql_core::{ColumnName, ObjectName, RoleName, SchemaName, SymbolId, SymbolInterner};
7
8use crate::{
9 AccessibleByTarget, ArgumentMetadata, CatalogCapabilities, CatalogObject, CatalogSnapshot,
10 CatalogSource, CatalogSourceKind, ColumnMetadata, DataTypeRef, Grant, GrantPrivilege, Grantee,
11 ObjectCommon, ObjectStatus, ObjectType, PackageMetadata, ProcedureMetadata, RoutineSignature,
12 SchemaCatalog, SequenceMetadata, SynonymTarget, TableMetadata, TriggerEvent, TriggerLevel,
13 TriggerMetadata, TriggerTiming, ViewMetadata,
14};
15
16#[derive(Debug)]
18pub struct SyntheticCatalogBuilder {
19 interner: SymbolInterner,
20 schemas: HashMap<SchemaName, SchemaCatalog>,
21 current_schema: SchemaName,
22}
23
24impl SyntheticCatalogBuilder {
25 pub fn new(schema_name: &str) -> Self {
27 let mut interner = SymbolInterner::default();
28 let schema = SchemaName::new(interner.intern(schema_name).unwrap());
29 let mut schemas = HashMap::new();
30 schemas.insert(schema, SchemaCatalog::default());
31
32 Self {
33 interner,
34 schemas,
35 current_schema: schema,
36 }
37 }
38
39 pub fn current_schema(&self) -> SchemaName {
41 self.current_schema
42 }
43
44 pub fn interner(&self) -> &SymbolInterner {
46 &self.interner
47 }
48
49 fn intern(&mut self, s: &str) -> SymbolId {
51 self.interner.intern(s).unwrap()
52 }
53
54 pub fn add_schema(&mut self, name: &str) -> SchemaName {
56 let schema = SchemaName::new(self.intern(name));
57 self.schemas.insert(schema, SchemaCatalog::default());
58 schema
59 }
60
61 pub fn add_table(&mut self, name: &str, columns: Vec<(&str, &str, bool)>) -> ObjectName {
63 let obj_name = ObjectName::new(self.intern(name));
64 let col_map: HashMap<ColumnName, ColumnMetadata> = columns
65 .into_iter()
66 .enumerate()
67 .map(|(i, (col_name, data_type, nullable))| {
68 let cn = ColumnName::new(self.intern(col_name));
69 (
70 cn,
71 ColumnMetadata {
72 name: cn,
73 position: i as u32 + 1,
74 data_type: DataTypeRef {
75 name: data_type.to_string(),
76 ..DataTypeRef::default()
77 },
78 nullable,
79 ..ColumnMetadata::default()
80 },
81 )
82 })
83 .collect();
84
85 let table = TableMetadata {
86 common: ObjectCommon {
87 owner: self.current_schema,
88 name: obj_name,
89 object_type: ObjectType::Table,
90 status: ObjectStatus::Valid,
91 ..ObjectCommon::default()
92 },
93 columns: col_map,
94 ..TableMetadata::default()
95 };
96
97 self.current_schema_catalog_mut()
98 .objects
99 .insert(obj_name, CatalogObject::Table(table));
100 obj_name
101 }
102
103 pub fn add_view(&mut self, name: &str, columns: Vec<(&str, &str, bool)>) -> ObjectName {
105 let obj_name = ObjectName::new(self.intern(name));
106 let col_map: HashMap<ColumnName, ColumnMetadata> = columns
107 .into_iter()
108 .enumerate()
109 .map(|(i, (col_name, data_type, nullable))| {
110 let cn = ColumnName::new(self.intern(col_name));
111 (
112 cn,
113 ColumnMetadata {
114 name: cn,
115 position: i as u32 + 1,
116 data_type: DataTypeRef {
117 name: data_type.to_string(),
118 ..DataTypeRef::default()
119 },
120 nullable,
121 ..ColumnMetadata::default()
122 },
123 )
124 })
125 .collect();
126
127 let view = ViewMetadata {
128 common: ObjectCommon {
129 owner: self.current_schema,
130 name: obj_name,
131 object_type: ObjectType::View,
132 status: ObjectStatus::Valid,
133 ..ObjectCommon::default()
134 },
135 columns: col_map,
136 ..ViewMetadata::default()
137 };
138
139 self.current_schema_catalog_mut()
140 .objects
141 .insert(obj_name, CatalogObject::View(view));
142 obj_name
143 }
144
145 pub fn add_package(
147 &mut self,
148 name: &str,
149 invoker_rights: bool,
150 accessible_by: Vec<(&str, &str)>,
151 ) -> ObjectName {
152 let obj_name = ObjectName::new(self.intern(name));
153
154 let access_list: Vec<AccessibleByTarget> = accessible_by
155 .into_iter()
156 .map(|(owner, obj)| AccessibleByTarget {
157 owner: Some(SchemaName::new(self.intern(owner))),
158 object_name: ObjectName::new(self.intern(obj)),
159 })
160 .collect();
161
162 let pkg = PackageMetadata {
163 common: ObjectCommon {
164 owner: self.current_schema,
165 name: obj_name,
166 object_type: ObjectType::Package,
167 status: ObjectStatus::Valid,
168 ..ObjectCommon::default()
169 },
170 authid_current_user: Some(invoker_rights),
171 accessible_by: access_list,
172 ..PackageMetadata::default()
173 };
174
175 self.current_schema_catalog_mut()
176 .objects
177 .insert(obj_name, CatalogObject::Package(pkg));
178 obj_name
179 }
180
181 pub fn add_procedure(
183 &mut self,
184 name: &str,
185 invoker_rights: bool,
186 args: Vec<ArgumentMetadata>,
187 ) -> ObjectName {
188 let obj_name = ObjectName::new(self.intern(name));
189
190 let proc = ProcedureMetadata {
191 common: ObjectCommon {
192 owner: self.current_schema,
193 name: obj_name,
194 object_type: ObjectType::Procedure,
195 status: ObjectStatus::Valid,
196 ..ObjectCommon::default()
197 },
198 signature: RoutineSignature {
199 routine_name: obj_name,
200 authid_current_user: Some(invoker_rights),
201 arguments: args,
202 ..RoutineSignature::default()
203 },
204 };
205
206 self.current_schema_catalog_mut()
207 .objects
208 .insert(obj_name, CatalogObject::Procedure(proc));
209 obj_name
210 }
211
212 pub fn add_sequence(&mut self, name: &str) -> ObjectName {
214 let obj_name = ObjectName::new(self.intern(name));
215
216 let seq = SequenceMetadata {
217 common: ObjectCommon {
218 owner: self.current_schema,
219 name: obj_name,
220 object_type: ObjectType::Sequence,
221 status: ObjectStatus::Valid,
222 ..ObjectCommon::default()
223 },
224 ..SequenceMetadata::default()
225 };
226
227 self.current_schema_catalog_mut()
228 .objects
229 .insert(obj_name, CatalogObject::Sequence(seq));
230 obj_name
231 }
232
233 pub fn add_trigger(
235 &mut self,
236 name: &str,
237 table_name: &str,
238 event: TriggerEvent,
239 timing: TriggerTiming,
240 level: TriggerLevel,
241 ) -> ObjectName {
242 let obj_name = ObjectName::new(self.intern(name));
243 let tbl = ObjectName::new(self.intern(table_name));
244
245 let trigger = TriggerMetadata {
246 common: ObjectCommon {
247 owner: self.current_schema,
248 name: obj_name,
249 object_type: ObjectType::Trigger,
250 status: ObjectStatus::Valid,
251 ..ObjectCommon::default()
252 },
253 target_owner: self.current_schema,
254 target_name: tbl,
255 events: vec![event],
256 timing,
257 level,
258 ..TriggerMetadata::default()
259 };
260
261 self.current_schema_catalog_mut()
262 .objects
263 .insert(obj_name, CatalogObject::Trigger(trigger));
264 obj_name
265 }
266
267 pub fn add_grant(
269 &mut self,
270 object_name: ObjectName,
271 privilege: GrantPrivilege,
272 grantee: Grantee,
273 grantable: bool,
274 ) {
275 let owner = self.current_schema;
276 self.current_schema_catalog_mut().grants.push(Grant {
277 object_owner: owner,
278 object_name,
279 privilege,
280 grantee,
281 grantable,
282 via_role: None,
283 with_hierarchy: false,
284 });
285 }
286
287 pub fn add_synonym(
289 &mut self,
290 name: &str,
291 target_schema: SchemaName,
292 target_name: &str,
293 public: bool,
294 ) {
295 let syn_id = self.intern(name);
296 let _syn_name = ObjectName::new(syn_id);
297 let target_obj = ObjectName::new(self.intern(target_name));
298 let _owner = self.current_schema;
299
300 self.current_schema_catalog_mut().synonyms.insert(
301 crate::SynonymName::new(syn_id),
302 SynonymTarget {
303 target_owner: Some(target_schema),
304 target_name: target_obj,
305 target_type: None,
306 db_link: None,
307 public_synonym: public,
308 },
309 );
310 }
311
312 pub fn build(self) -> CatalogSnapshot {
314 CatalogSnapshot {
315 schemas: self.schemas,
316 profile: plsql_core::AnalysisProfile::default(),
317 capabilities: CatalogCapabilities {
318 can_query_all_views: true,
319 can_query_dba_views: false,
320 can_use_dbms_metadata: false,
321 can_read_source: true,
322 plscope_enabled: false,
323 can_query_scheduler: false,
324 can_query_roles_and_grants: true,
325 warnings: vec![],
326 },
327 generated_at: Utc::now(),
328 source: CatalogSource {
329 kind: CatalogSourceKind::SyntheticTestCatalog,
330 description: Some("Synthetic test catalog".to_string()),
331 ..CatalogSource::default()
332 },
333 interner: self.interner,
334 editions: Vec::new(),
335 known_users: None,
338 }
339 }
340
341 fn current_schema_catalog_mut(&mut self) -> &mut SchemaCatalog {
342 self.schemas.get_mut(&self.current_schema).unwrap()
343 }
344}
345
346pub fn billing_schema() -> CatalogSnapshot {
353 let mut builder = SyntheticCatalogBuilder::new("BILLING");
354
355 let customers = builder.add_table(
357 "CUSTOMERS",
358 vec![
359 ("CUSTOMER_ID", "NUMBER", false),
360 ("NAME", "VARCHAR2", false),
361 ("EMAIL", "VARCHAR2", true),
362 ("LEGACY_SEGMENT", "VARCHAR2", true),
363 ("STATUS", "VARCHAR2", false),
364 ],
365 );
366
367 let invoices = builder.add_table(
368 "INVOICES",
369 vec![
370 ("INVOICE_ID", "NUMBER", false),
371 ("CUSTOMER_ID", "NUMBER", false),
372 ("AMOUNT", "NUMBER", false),
373 ("STATUS", "VARCHAR2", false),
374 ("CREATED_DATE", "DATE", false),
375 ],
376 );
377
378 let invoice_lines = builder.add_table(
379 "INVOICE_LINES",
380 vec![
381 ("LINE_ID", "NUMBER", false),
382 ("INVOICE_ID", "NUMBER", false),
383 ("DESCRIPTION", "VARCHAR2", false),
384 ("QTY", "NUMBER", false),
385 ("UNIT_PRICE", "NUMBER", false),
386 ],
387 );
388
389 let _payments = builder.add_table(
390 "PAYMENTS",
391 vec![
392 ("PAYMENT_ID", "NUMBER", false),
393 ("INVOICE_ID", "NUMBER", false),
394 ("AMOUNT", "NUMBER", false),
395 ("PAYMENT_DATE", "DATE", false),
396 ],
397 );
398
399 let _balance_view = builder.add_view(
401 "V_CUSTOMER_BALANCE",
402 vec![
403 ("CUSTOMER_ID", "NUMBER", false),
404 ("NAME", "VARCHAR2", false),
405 ("TOTAL_INVOICED", "NUMBER", true),
406 ("TOTAL_PAID", "NUMBER", true),
407 ("BALANCE", "NUMBER", true),
408 ],
409 );
410
411 let billing_api = builder.add_package("BILLING_API", false, vec![]);
413 let _payment_proc =
414 builder.add_package("PAYMENT_PROCESSOR", true, vec![("BILLING", "BILLING_API")]);
415
416 builder.add_procedure("GENERATE_INVOICE", false, vec![]);
418
419 builder.add_sequence("INVOICE_SEQ");
421
422 let reader_role = RoleName::new(builder.intern("reader"));
424 builder.add_grant(
425 customers,
426 GrantPrivilege::Select,
427 Grantee::Role(reader_role),
428 false,
429 );
430 builder.add_grant(
431 invoices,
432 GrantPrivilege::Select,
433 Grantee::Role(reader_role),
434 false,
435 );
436 builder.add_grant(
437 invoice_lines,
438 GrantPrivilege::Select,
439 Grantee::Role(reader_role),
440 false,
441 );
442 builder.add_grant(billing_api, GrantPrivilege::Execute, Grantee::Public, false);
443
444 builder.build()
445}