1pub use compile::sql::host_type;
4pub use rt::sql::*;
5
6#[cfg(test)]
7mod tests {
8 use super::*;
9 use crate::Execute;
10 use crate::layout::Resolved;
11
12 fn types(data: &str, names: &[&str]) -> Vec<Result<HostType, String>> {
13 let source = format!(
14 " IDENTIFICATION DIVISION.\n PROGRAM-ID. T.\n DATA DIVISION.\n WORKING-STORAGE SECTION.\n{data} PROCEDURE DIVISION.\n GOBACK.\n"
15 );
16 let compiled = crate::compile(syntax::parse(&source).expect("parses"), &[]).expect("compiles");
17 let layout = &compiled.layout;
18 names
19 .iter()
20 .map(|n| match layout.resolve(n, &[], syntax::Pos::default()) {
21 Ok(Resolved::Item(i)) => host_type(layout, i),
22 other => panic!("{n}: {other:?}"),
23 })
24 .collect()
25 }
26
27 #[test]
28 fn cobol_declarations_as_db2_reads_them() {
29 let got = types(
30 concat!(
31 " 01 H PIC S9(4) COMP.\n",
32 " 01 I PIC S9(9) COMP-5.\n",
33 " 01 B PIC S9(18) BINARY.\n",
34 " 01 D PIC S9(5)V99 COMP-3.\n",
35 " 01 Z PIC S9(5)V99 SIGN LEADING SEPARATE.\n",
36 " 01 C PIC X(10).\n",
37 " 01 F COMP-2.\n",
38 " 01 V.\n",
39 " 49 V-LEN PIC S9(4) COMP.\n",
40 " 49 V-TEXT PIC X(30).\n",
41 " 01 S.\n",
42 " 05 S-ID PIC S9(9) COMP.\n",
43 " 05 S-NAME PIC X(20).\n",
44 ),
45 &["H", "I", "B", "D", "Z", "C", "F", "V", "S"],
46 );
47 assert_eq!(got[0], Ok(HostType::SmallInt { signed: true }));
48 assert_eq!(got[1], Ok(HostType::Integer { signed: true }));
49 assert_eq!(got[2], Ok(HostType::BigInt { signed: true }));
50 assert_eq!(got[3], Ok(HostType::Decimal { digits: 7, scale: 2, signed: true }));
51 assert!(matches!(got[4], Ok(HostType::Zoned { digits: 7, scale: 2, signed: true, sign: Some(_) })));
52 assert_eq!(got[5], Ok(HostType::Char(10)));
53 assert_eq!(got[6], Ok(HostType::Double));
54 assert_eq!(got[7], Ok(HostType::VarChar(30)));
55 let Ok(HostType::Structure(members)) = &got[8] else { panic!("{:?}", got[8]) };
56 assert_eq!(members.iter().map(|(_, t)| t.clone()).collect::<Vec<_>>(), [HostType::Integer { signed: true }, HostType::Char(20)]);
57 }
58
59 #[test]
60 fn dbcs_items_bind_as_graphic_and_vargraphic() {
61 let data = " 01 G PIC G(5) DISPLAY-1.\n 01 V.\n 49 V-LEN PIC S9(4) COMP.\n 49 V-TEXT PIC G(30) DISPLAY-1.\n 01 E PIC GBG DISPLAY-1.\n";
62 let got = types(data, &["G", "V", "E"]);
63 assert_eq!((&got[0], &got[1]), (&Ok(HostType::Graphic(5)), &Ok(HostType::VarGraphic(30))));
64 assert!(got[2].is_err(), "a DBCS PICTURE with B has no SQL type");
65 }
66
67 #[test]
68 fn declarations_with_no_sql_type() {
69 let got = types(" 01 E PIC ZZ9.99.\n 01 P USAGE POINTER.\n 01 BS PIC S9(3)V9 COMP.\n", &["E", "P", "BS"]);
70 assert!(got.iter().all(Result::is_err), "{got:?}");
71 }
72
73 mod live {
76 use super::*;
77 use std::cell::RefCell;
78 use std::rc::Rc;
79
80 fn url() -> Option<String> {
81 std::env::var("IRONWORK_PG_URL").ok()
82 }
83
84 const SCHEMA: &str = "DROP TABLE IF EXISTS emp; \
85 CREATE TABLE emp (id integer PRIMARY KEY, name char(10) NOT NULL, amt numeric(7,2), stamp timestamp); \
86 INSERT INTO emp VALUES (1, 'ADAMS', 100.50, '2020-01-02 03:04:05.5'), (2, 'BAKER', NULL, NULL), (3, 'CLARK', 30, NULL); \
87 CREATE SCHEMA IF NOT EXISTS sysibm; \
88 CREATE OR REPLACE VIEW sysibm.sysdummy1 AS SELECT 'Y'::char(1) AS ibmreqd;";
89
90 const PROGRAM: &str = concat!(
91 " IDENTIFICATION DIVISION.\n",
92 " PROGRAM-ID. PGQ5.\n",
93 " DATA DIVISION.\n",
94 " WORKING-STORAGE SECTION.\n",
95 " EXEC SQL INCLUDE SQLCA END-EXEC.\n",
96 " 01 WS-ID PIC S9(9) COMP VALUE 2.\n",
97 " 01 WS-NAME PIC X(10).\n",
98 " 01 WS-AMT PIC S9(5)V99 COMP-3.\n",
99 " 01 WS-IND PIC S9(4) COMP.\n",
100 " 01 WS-STAMP PIC X(26).\n",
101 " 01 WS-Y PIC X.\n",
102 " 01 WS-COUNT PIC S9(9) COMP.\n",
103 " 01 E-AMT PIC -9(5).99.\n",
104 " 01 E-CODE PIC -9(3).\n",
105 " 01 E-NUM PIC -9(3).\n",
106 " EXEC SQL DECLARE C1 CURSOR FOR\n",
107 " SELECT NAME, AMT FROM EMP WHERE ID >= :WS-ID\n",
108 " ORDER BY ID\n",
109 " END-EXEC.\n",
110 " PROCEDURE DIVISION.\n",
111 " MOVE 1 TO WS-ID.\n",
112 " EXEC SQL OPEN C1 END-EXEC.\n",
113 " PERFORM UNTIL SQLCODE NOT = 0\n",
114 " EXEC SQL FETCH C1 INTO :WS-NAME, :WS-AMT:WS-IND\n",
115 " END-EXEC\n",
116 " IF SQLCODE = 0\n",
117 " MOVE WS-AMT TO E-AMT\n",
118 " MOVE WS-IND TO E-NUM\n",
119 " DISPLAY WS-NAME E-AMT E-NUM\n",
120 " END-IF\n",
121 " END-PERFORM.\n",
122 " EXEC SQL CLOSE C1 END-EXEC.\n",
123 " EXEC SQL SELECT STAMP INTO :WS-STAMP FROM EMP\n",
124 " WHERE ID = 1 END-EXEC.\n",
125 " DISPLAY WS-STAMP.\n",
126 " EXEC SQL INSERT INTO EMP (ID, NAME) VALUES (1, 'DUP')\n",
127 " END-EXEC.\n",
128 " MOVE SQLCODE TO E-CODE.\n",
129 " DISPLAY 'INSERT ' E-CODE ' ' SQLSTATE.\n",
130 " EXEC SQL UPDATE EMP SET AMT = AMT + 1 WHERE ID = 99\n",
131 " END-EXEC.\n",
132 " MOVE SQLCODE TO E-CODE.\n",
133 " DISPLAY 'UPDATE ' E-CODE.\n",
134 " EXEC SQL SELECT COUNT(*) INTO :WS-COUNT FROM EMP\n",
135 " WHERE AMT < 200 WITH UR END-EXEC.\n",
136 " MOVE WS-COUNT TO E-NUM.\n",
137 " DISPLAY 'COUNT ' E-NUM.\n",
138 " EXEC SQL SELECT IBMREQD INTO :WS-Y\n",
139 " FROM SYSIBM.SYSDUMMY1 END-EXEC.\n",
140 " DISPLAY 'DUMMY ' WS-Y.\n",
141 " EXEC SQL SELECT NAME INTO :WS-NAME FROM EMP END-EXEC.\n",
142 " MOVE SQLCODE TO E-CODE.\n",
143 " DISPLAY 'MANY ' E-CODE.\n",
144 " GOBACK.\n",
145 );
146
147 const EXPECTED: &str = concat!(
148 "ADAMS 00100.50 000\n",
149 "BAKER 00100.50-001\n",
150 "CLARK 00030.00 000\n",
151 "2020-01-02-03.04.05.500000\n",
152 "INSERT -803 23505\n",
153 "UPDATE 100\n",
154 "COUNT 002\n",
155 "DUMMY Y\n",
156 "MANY -811\n",
157 );
158
159 struct Sink(Rc<RefCell<Vec<u8>>>);
160
161 impl std::io::Write for Sink {
162 fn write(&mut self, b: &[u8]) -> std::io::Result<usize> {
163 self.0.borrow_mut().extend_from_slice(b);
164 Ok(b.len())
165 }
166 fn flush(&mut self) -> std::io::Result<()> {
167 Ok(())
168 }
169 }
170
171 fn run(mut database: Box<dyn Database + '_>) -> Result<String, String> {
172 let compiled = crate::compile(syntax::parse(PROGRAM).expect("parses"), &[]).expect("compiles");
173 let (mut out, mut err) = (Vec::new(), Vec::new());
174 let ran = compiled.execute_with(crate::unit::Library::default(), crate::files::Dds::default(), None, crate::unit::Clock::System, Some(database.as_mut()), &mut out, &mut err);
175 ran.map(|_| String::from_utf8(out).expect("DISPLAY writes text")).map_err(|a| format!("{}: {}", a.code, a.message))
176 }
177
178 #[test]
179 fn a_recorded_postgresql_run_replays_identically() {
180 let Some(url) = url() else { return };
181 let mut setup = Postgres::connect(&url, None).expect("connects");
182 setup.load_script(SCHEMA).expect("the schema loads");
183 let postgres = Postgres::connect(&url, None).expect("connects");
184 let source = postgres.source().to_owned();
185 assert!(source.starts_with("PostgreSQL 14"), "{source}");
186 let recording = Rc::new(RefCell::new(Vec::new()));
187 let recorder = Recorder::new(Box::new(postgres), Box::new(Sink(recording.clone())), &source).expect("records");
188 assert_eq!(run(Box::new(recorder)).as_deref(), Ok(EXPECTED));
189 let text = String::from_utf8(recording.borrow().clone()).expect("a recording is text");
190 let replayed = run(Box::new(Replay::parse(&text, false).expect("the recording parses")));
191 assert_eq!(replayed.as_deref(), Ok(EXPECTED), "{text}");
192 }
193 }
194}