Changeset 23
- Timestamp:
- 09/07/08 23:57:57 (3 years ago)
- Location:
- trunk/epic
- Files:
-
- 2 modified
-
epic.sql (modified) (7 diffs)
-
test/test_globals.sql (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/epic/epic.sql
r22 r23 227 227 228 228 229 CREATE OR REPLACE FUNCTION _ensure_globals() RETURNS boolean AS $$229 CREATE OR REPLACE FUNCTION test._ensure_globals() RETURNS boolean AS $$ 230 230 BEGIN 231 231 SET client_min_messages = warning; 232 232 233 233 BEGIN 234 CREATE SEQUENCE _global_ids;234 CREATE SEQUENCE test._global_ids; 235 235 EXCEPTION WHEN duplicate_table THEN 236 236 NULL; … … 240 240 END; 241 241 $$ LANGUAGE plpgsql; 242 SELECT * FROM _ensure_globals(); 243 DROP FUNCTION _ensure_globals(); 244 245 246 -- Note this is in the public schema!! 247 CREATE OR REPLACE FUNCTION _global_record(tablename text, creator text) RETURNS record AS $$ 248 DECLARE 249 result record; 250 record_stmt text; 251 BEGIN 252 EXECUTE 'SELECT NULL::text AS __name__, NULL::text AS __create__, ' 253 || 'NULL::text AS __record__, NULL::text AS __iter__, ' 254 || 'NULL::text AS __attributes__, 0::int AS __len__, ' 255 || '* FROM ' || tablename || ' LIMIT 1' INTO result; 256 257 -- We add these values outside the EXECUTE in case the TEMP table has no rows. 258 result.__name__ := tablename; 259 result.__create__ := creator; 260 result.__record__ := 'SELECT * FROM _global_record(''' || tablename || ''', ''' || creator || ''')'; 261 result.__iter__ := 'SELECT * FROM ' || tablename; 262 result.__attributes__ := 'SELECT attname FROM test.attributes(''' || tablename || ''')'; 263 EXECUTE 'SELECT COUNT(*) FROM ' || tablename INTO result.__len__; 264 265 RETURN result; 266 END; 267 $$ LANGUAGE plpgsql; 268 269 270 -- Note this is in the public schema!! 271 CREATE OR REPLACE FUNCTION global(call text) RETURNS record AS $$ 272 -- Stores the given call's output in a TEMP table, and returns it as a record. 273 -- If the call produces several rows, only the first record is returned. 242 SELECT * FROM test._ensure_globals(); 243 DROP FUNCTION test._ensure_globals(); 244 245 246 CREATE OR REPLACE FUNCTION test.global(call text, name text) RETURNS text AS $$ 247 -- Stores the given call's output in a TEMP table, and returns the TEMP table name. 274 248 -- 275 249 -- 'call' can be any SELECT, table, view, or procedure that returns records. 250 DECLARE 251 tablename text; 252 creator text; 253 BEGIN 254 IF name IS NULL THEN 255 tablename := '_global_' || nextval('_global_ids'); 256 ELSE 257 tablename := name; 258 END IF; 259 260 BEGIN 261 EXECUTE 'DROP TABLE ' || tablename; 262 EXCEPTION WHEN undefined_table THEN 263 NULL; 264 END; 265 266 creator := test.statement(call); 267 EXECUTE 'CREATE TEMP TABLE ' || tablename || ' WITHOUT OIDS AS ' || creator; 268 EXECUTE 'COMMENT ON TABLE ' || tablename || ' IS ' || quote_literal(creator); 269 RETURN tablename; 270 END; 271 $$ LANGUAGE plpgsql; 272 273 CREATE OR REPLACE FUNCTION test.global(call text) RETURNS text AS $$ 274 SELECT global FROM test.global($1, NULL); 275 $$ LANGUAGE SQL; 276 277 CREATE OR REPLACE FUNCTION test.get(p_tablename text, p_offset int) RETURNS record AS $$ 278 -- Returns a record (the first one, by default) from the given global table. 276 279 -- 277 280 -- The returned record includes the following additional attributes: … … 280 283 -- such as assert_column, assert_values, and assert_empty (since no 281 284 -- procedural languages support passing records as args). 282 -- * __create__ (text): The SQL statement used to construct the table. 283 -- * __record__ (text): The SQL statement used to construct the returned record. 284 -- Use this to copy the returned record (perhaps in another function). 285 -- Example: EXECUTE g.__record__ INTO g2; 286 -- * __iter__ (text): An SQL string to SELECT * FROM the TEMP table. 287 -- Example: FOR record IN EXECUTE g.__iter__ 288 -- * __attributes__ (text): The SQL string for TEMP table column names. 289 -- Example: FOR colname IN EXECUTE g.__attributes__ 290 -- * __len__ (int): The number of rows in the TEMP table. This value 291 -- is NOT updated if you change the table after its creation. 292 DECLARE 293 tablename text; 294 creator text; 285 DECLARE 295 286 result record; 296 BEGIN 297 tablename := '_global_' || nextval('_global_ids'); 298 creator := test.statement(call); 299 EXECUTE 'CREATE TEMP TABLE ' || tablename || ' AS ' || creator; 300 result := _global_record(tablename, creator); 287 rownum int; 288 BEGIN 289 IF p_offset IS NULL OR p_offset < 0 THEN 290 rownum := 0; 291 ELSE 292 rownum := p_offset; 293 END IF; 294 295 EXECUTE 'SELECT *, NULL::text AS __name__ FROM ' || p_tablename || ' LIMIT 1 OFFSET ' || rownum INTO result; 296 297 -- We add these values outside the EXECUTE in case the SELECT returned no rows. 298 result.__name__ := p_tablename; 299 301 300 RETURN result; 302 301 END; 303 302 $$ LANGUAGE plpgsql; 304 303 304 CREATE OR REPLACE FUNCTION test.get(p_tablename text) RETURNS record AS $$ 305 DECLARE 306 result record; 307 BEGIN 308 EXECUTE 'SELECT *, NULL::text AS __name__ FROM ' || p_tablename || ' LIMIT 1' INTO result; 309 310 -- We add these values outside the EXECUTE in case the SELECT returned no rows. 311 result.__name__ := p_tablename; 312 313 RETURN result; 314 END; 315 $$ LANGUAGE plpgsql; 316 317 318 CREATE OR REPLACE FUNCTION test.constructor(tablename text) RETURNS text AS $$ 319 -- Return the SQL statement used to construct the given global table. 320 SELECT obj_description(pgc.oid, 'pg_class') FROM pg_class pgc WHERE relname = $1; 321 $$ LANGUAGE SQL; 322 323 324 CREATE OR REPLACE FUNCTION test.len(tablename text) RETURNS int AS $$ 325 -- Return the number of rows in the given table. 326 DECLARE 327 num int; 328 BEGIN 329 EXECUTE 'SELECT COUNT(*) FROM ' || tablename INTO num; 330 RETURN num; 331 END 332 $$ LANGUAGE plpgsql; 333 334 335 CREATE OR REPLACE FUNCTION test.iter(tablename text) RETURNS text AS $$ 336 -- Return SQL to retrieve all rows in the given table. 337 SELECT 'SELECT * FROM ' || $1 338 $$ LANGUAGE sql; 339 305 340 306 341 CREATE OR REPLACE FUNCTION test.attributes(tablename text) RETURNS SETOF pg_attribute AS $$ 307 342 DECLARE 308 343 rec record; 344 seen int := 0; 309 345 BEGIN 310 346 FOR rec IN … … 314 350 AND attnum >= 1 315 351 LOOP 352 seen := seen + 1; 316 353 RETURN NEXT rec; 317 354 END LOOP; 318 END; 319 $$ LANGUAGE plpgsql; 320 321 322 CREATE OR REPLACE FUNCTION typename(elem anyelement) RETURNS text AS $$ 355 356 IF seen = 0 THEN 357 RAISE EXCEPTION '% has no attributes.', quote_literal(tablename); 358 END IF; 359 END; 360 $$ LANGUAGE plpgsql; 361 362 363 CREATE OR REPLACE FUNCTION test.typename(elem anyelement) RETURNS text AS $$ 323 364 -- Return the typename of the given element. 324 365 DECLARE … … 345 386 output_record test.results%ROWTYPE; 346 387 splitpoint int; 388 old_search_path text; 347 389 BEGIN 348 390 SELECT module INTO modulename FROM test.testnames WHERE name = testname; … … 350 392 351 393 BEGIN 394 -- Allow test.* functions to be referenced without a schema name during this transaction. 395 PERFORM set_config('search_path', 'test, ' || current_setting('search_path'), true); 352 396 EXECUTE 'SELECT * FROM test.' || testname || '();'; 353 397 EXCEPTION WHEN OTHERS THEN … … 629 673 RETURN; 630 674 END; 631 RAISE EXCEPTION 'Call: ''%'' did not raise an error.', call;675 RAISE EXCEPTION 'Call: % did not raise an error.', quote_literal(call); 632 676 END; 633 677 $$ LANGUAGE plpgsql; -
trunk/epic/test/test_globals.sql
r22 r23 4 4 CREATE OR REPLACE FUNCTION test._test_global() RETURNS VOID AS $$ 5 5 DECLARE 6 g text; 7 g2 text; 6 8 rec record; 7 9 trec record; 8 10 BEGIN 9 rec:= global('pg_namespace WHERE nspname = ''test'';');11 g := global('pg_namespace WHERE nspname = ''test'';'); 10 12 11 -- The result of global() should be a normal record. 13 -- The returned string MUST be the name of the TEMP table. 14 PERFORM test.assert(g LIKE E'\_global\_%', g || ' not like _global'); 15 16 rec := get(g); 17 18 -- The result of get() should be a normal record. 12 19 PERFORM test.assert_equal(rec.nspname, 'test'); 13 20 … … 15 22 PERFORM test.assert(rec.__name__ LIKE E'\_global\_%', rec.__name__ || ' not like _global'); 16 23 17 -- The .__name__MUST reference a temporary table with the same fields.18 EXECUTE 'SELECT * FROM ' || rec.__name__INTO trec;24 -- The global MUST reference a temporary table with the same fields. 25 EXECUTE 'SELECT * FROM ' || g INTO trec; 19 26 PERFORM test.assert_equal(trec.nspname, 'test'); 20 27 PERFORM test.assert_equal(trec.nspowner, rec.nspowner); 21 28 PERFORM test.assert_equal(trec.nspacl, rec.nspacl); 22 29 23 -- The returned record MUST possess a .__create__ attribute.24 PERFORM test.assert_equal( rec.__create__, 'SELECT * FROM pg_namespace WHERE nspname = ''test''');30 -- The TEMP table MUST possess its own constructor SQL string in a COMMENT 31 PERFORM test.assert_equal(constructor(g), 'SELECT * FROM pg_namespace WHERE nspname = ''test'''); 25 32 26 -- T he returned record MUST possess a .__record__ attribute.27 PERFORM test.assert_equal(rec.__record__,28 'SELECT * FROM _global_record(''' || rec.__name__ || ''', ''' || rec.__create__ || ''')');33 -- Test the iter() function. 34 g2 := global('iter(''' || g || ''')'); 35 PERFORM test.assert_not_empty(g2); 29 36 30 -- T he returned record MUST possess an .__iter__ attribute.31 PERFORM test.assert_ equal(rec.__iter__, 'SELECT * FROM ' || rec.__name__);32 PERFORM test.assert_not_empty(rec.__iter__);37 -- Test the attributes() function. 38 PERFORM test.assert_column('SELECT attname FROM attributes(''' || g || ''')', 39 ARRAY['nspname', 'nspowner', 'nspacl']); 33 40 34 -- The returned record MUST possess an .__attributes__ attribute. 35 PERFORM test.assert_equal(rec.__attributes__, 'SELECT attname FROM test.attributes(''' || rec.__name__ || ''')'); 36 PERFORM test.assert_column(rec.__attributes__, ARRAY['nspname', 'nspowner', 'nspacl']); 37 38 -- The returned record MUST possess a .__len__ attribute. 39 PERFORM test.assert_equal(rec.__len__, 1); 41 -- Test the len() function. 42 PERFORM test.assert_equal(len(g), 1); 40 43 41 44 -- Raise an exception to test deletion of the TEMP table ON COMMIT 42 RAISE EXCEPTION '%', rec.__name__;45 RAISE EXCEPTION '%', g; 43 46 END; 44 47 $$ LANGUAGE plpgsql;
