Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: mauzo/pg-ordered
base: fb1890184f
...
head fork: mauzo/pg-ordered
compare: 8ac7335123
  • 9 commits
  • 4 files changed
  • 0 commit comments
  • 1 contributor
Commits on Dec 02, 2011
@mauzo Let's try some tests, shall we? caec76b
@mauzo A language with strings but no interpolation...
is just crazy. Even printf-style is better than nothing. (9 has
format(), which is sprintf, but I don't want to require 9 and I don't
really want to write a whole printf implementation in PL/pgSQL.)
4803dff
@mauzo Don't rev the seq unnecessarily. 515573e
@mauzo A btree always has a root.
So there's no need to allow for testing btrees without one.
a0dc799
Commits on Dec 03, 2011
@mauzo pgTAP uses no_plan/finish for done_testing().
This is different from Test::More, where no_plan outputs an explicit
'1..0' plan at the start.

Also, I mustn't use savepoints, since they roll back pgTAP's internal
state. (Duh.) This won't be fixable unless/til Pg gets autonomous txns.
982da25
@mauzo Test that splits work properly. 15c7db9
@mauzo Use the correct fill factor for splits. 6779850
@mauzo _do.
This is a (somewhat poor) substitute for 9's DO. It creates, executes
and drops a temporary function with the given contents.
1ed11f9
@mauzo Use the optimised idx() from contrib/intarray.
This only works for 32-bit integers, so if/when we move to bigints we'll
lose the optimisation.
8ac7335
Showing with 232 additions and 10 deletions.
  1. +30 −10 btree.sql
  2. +10 −0 t/00schema.pg
  3. +148 −0 t/btree.pg
  4. +44 −0 util.sql
View
40 btree.sql
@@ -22,8 +22,16 @@ CREATE TABLE btree (
CHECK( id = 0 OR leaf IS NOT NULL )
);
CREATE INDEX btree_kids ON btree USING gin (kids);
-INSERT INTO btree (kids, leaf) VALUES ('{}', true);
-INSERT INTO btree VALUES (0, array[1, 8], NULL);
+
+CREATE FUNCTION reset_btree (integer) RETURNS void
+ LANGUAGE sql VOLATILE
+ SECURITY DEFINER
+ SET search_path FROM CURRENT
+ AS $fn$
+ TRUNCATE btree RESTART IDENTITY;
+ INSERT INTO btree (kids, leaf) VALUES ('{}', true);
+ INSERT INTO btree VALUES (0, array[1, $1], NULL);
+ $fn$;
-- since this is just a temporary arrangement until users can create
-- their own trees, allow full access.
@@ -32,7 +40,7 @@ GRANT ALL ON SEQUENCE btree_id_seq TO PUBLIC;
-- array functions
-CREATE FUNCTION ix(anyarray, anyelement) RETURNS integer
+CREATE FUNCTION idx(anyarray, anyelement) RETURNS integer
LANGUAGE sql IMMUTABLE
AS $fn$
SELECT n
@@ -40,6 +48,18 @@ CREATE FUNCTION ix(anyarray, anyelement) RETURNS integer
WHERE $1[n] = $2
$fn$;
+-- use the optimised version from contrib/intarray if we've got it
+SELECT _do($do$
+ BEGIN
+ CREATE FUNCTION idx (integer[], integer) RETURNS integer
+ LANGUAGE C STRICT IMMUTABLE
+ AS '$libdir/_int';
+ EXCEPTION
+ WHEN undefined_file THEN
+ NULL;
+ END;
+$do$);
+
-- metapage functions
CREATE TYPE btree_meta AS (
@@ -79,7 +99,7 @@ CREATE FUNCTION btree_x (btree, integer) RETURNS btree_x
AS $fn$
SELECT btmeta(), $1.id,
coalesce(array_length($1.kids, 1), 0), $1.kids,
- $1.leaf, ix($1.kids, $2);
+ $1.leaf, idx($1.kids, $2);
$fn$;
CREATE FUNCTION btree_x (btree) RETURNS btree_x
@@ -266,7 +286,6 @@ CREATE FUNCTION insert(
BEGIN
m := btmeta();
seq := pg_get_serial_sequence('btree', 'id');
- k := coalesce(val, nextval(seq));
IF before IS NULL THEN
n := last_child(m.root)::btree_x;
@@ -274,22 +293,23 @@ CREATE FUNCTION insert(
ELSE
n := btfind(before, isk);
- IF n IS NULL THEN
- RAISE 'not in btree: % (%)', before, isk;
+ IF n.id IS NULL THEN
+ RAISE 'not in btree: %', before;
END IF;
END IF;
+ k := coalesce(val, nextval(seq));
RAISE NOTICE 'inserting % into % at %', k, n.id, n.ix;
n.ks := n.ks[1:n.ix-1] || k || n.ks[n.ix:n.nk];
n := reset(n);
- IF n.nk < 8 THEN
+ IF n.nk < m.fill THEN
PERFORM update(n);
ELSE
- s.ks := n.ks[1:4];
+ s.ks := n.ks[1:m.fill/2];
s.leaf := n.leaf;
- n.ks := n.ks[5:8];
+ n.ks := n.ks[(m.fill/2 + 1):m.fill];
s := update(s);
n := update(n);
View
10 t/00schema.pg
@@ -0,0 +1,10 @@
+-- vi: set syn=sql :
+
+BEGIN;
+SET search_path TO tap;
+
+SELECT plan(2);
+SELECT has_schema('ordered1');
+SELECT has_type('ordered1', 'ordered'::name);
+
+ROLLBACK;
View
148 t/btree.pg
@@ -0,0 +1,148 @@
+-- vi: set syn=sql:
+BEGIN;
+
+CREATE SCHEMA t;
+SET search_path TO t, tap, ordered1;
+
+CREATE FUNCTION perform (anyelement) RETURNS void
+ LANGUAGE sql VOLATILE
+ AS $fn$
+ SELECT $1;
+ COMMENT ON FUNCTION perform (anyelement) IS NULL;
+ $fn$;
+
+CREATE FUNCTION intrp (INOUT str text, VARIADIC sub anyarray)
+ LANGUAGE plpgsql STABLE
+ AS $fn$
+ BEGIN
+ FOR i IN 1 .. array_length(sub, 1) LOOP
+ -- regexp_replace since replace won't do just one
+ str := regexp_replace(str, '%', sub[i]::text);
+ END LOOP;
+ END;
+ $fn$;
+
+CREATE FUNCTION btree_is (integer, integer, text, text) RETURNS text
+ LANGUAGE sql VOLATILE
+ AS $fn$
+ SELECT bag_eq(
+ $wnt$ SELECT * FROM btree $wnt$,
+ intrp(
+ $$ VALUES (0, array[%, %]::integer[], null), % $$,
+ $1::text, $2::text, $3
+ ),
+ $4
+ );
+ $fn$;
+
+CREATE FUNCTION btree_has (btree, text) RETURNS text
+ LANGUAGE sql VOLATILE
+ AS $fn$
+ SELECT set_has(
+ $$ SELECT * FROM btree $$,
+ intrp('SELECT (%::btree).*', quote_literal($1)),
+ $2
+ );
+ $fn$;
+
+CREATE FUNCTION notthere (text, integer, text) RETURNS SETOF text
+ LANGUAGE plpgsql
+ AS $fn$
+ DECLARE
+ s bigint;
+ BEGIN
+ s := currval('btree_id_seq');
+
+ RETURN NEXT throws_ok(
+ intrp('SELECT % (%)', $1, $2::text),
+ 'P0001', intrp('not in btree: %', $2),
+ intrp('% (%) throws correct exception', $1, $3)
+ );
+ RETURN NEXT is(
+ currval('btree_id_seq'), s,
+ intrp('% (%) doesn''t inc sequence', $1, $3)
+ );
+ RETURN;
+ END;
+ $fn$;
+
+SELECT no_plan();
+SELECT reset_btree(8);
+
+SELECT btree_is(1, 8, $$
+ (1, '{}', true)
+$$, 'initial empty btree');
+
+SELECT is(insert(null), 2, 'insert returns next seq value');
+
+SELECT btree_is(1, 8, $$
+ (1, '{2}', true)
+$$, 'inserted into root node');
+
+SELECT perform(insert(null));
+SELECT btree_is(1, 8, $$
+ (1, '{2,3}', true)
+$$, 'insert(null) inserts at end');
+
+SELECT perform(insert(2));
+SELECT btree_is(1, 8, $$
+ (1, '{4,2,3}', true)
+$$, 'insert(i) inserts before i');
+
+SELECT notthere('insert', 5, 'not there');
+SELECT notthere('insert', 1, 'root id');
+SELECT notthere('insert', 8, 'root fill');
+
+SELECT perform(insert(null)) FROM generate_series(1,4);
+SELECT btree_is(1, 8, $$
+ (1, '{4,2,3,5,6,7,8}', true)
+$$, '7 items will fit without splitting');
+
+SELECT perform(insert(null));
+SELECT btree_is(11, 8, $$
+ (1, '{6,7,8,9}', true),
+ (10, '{4,2,3,5}', true),
+ (11, '{10,1}', false)
+$$, '8 items will split');
+
+SELECT notthere('insert', 10, 'non-root id');
+
+SELECT perform(insert(2));
+SELECT btree_has(
+ (10, '{4,12,2,3,5}', true),
+ 'insert(i) finds the right child');
+
+SELECT perform(insert(null));
+SELECT btree_has(
+ (1, '{6,7,8,9,13}', true),
+ 'insert(null) finds the right child');
+
+SELECT perform(insert(null));
+SELECT perform(insert(null));
+SELECT perform(insert(null));
+SELECT btree_is(11, 8, $$
+ (1, '{13,14,15,16}', true),
+ (17, '{6,7,8,9}', true),
+ (10, '{4,12,2,3,5}', true),
+ (11, '{10,17,1}', false)
+$$, 'overflow splits rather than stealing');
+
+SELECT reset_btree(256);
+SELECT btree_is(1, 256, $$
+ (1, '{}', true)
+$$, 'initial btree (order 256)');
+
+SELECT perform(insert(null)) FROM generate_series(1,255);
+SELECT bag_eq(
+ $$ SELECT id FROM btree $$,
+ $$ VALUES (0), (1) $$,
+ 'order-256 btree will take 255 values without splitting');
+
+SELECT perform(insert(null));
+SELECT bag_eq(
+ $$ SELECT id FROM btree $$,
+ $$ VALUES (0), (1), (258), (259) $$,
+ 'order-256 btree splits at 256 entries');
+
+SELECT * FROM finish();
+ROLLBACK;
View
44 util.sql
@@ -43,3 +43,47 @@ CREATE FUNCTION do_execs (subst text[], cmds text[])
END;
$$;
+CREATE FUNCTION _do (sql text) RETURNS void
+ LANGUAGE plpgsql VOLATILE
+ AS $fn$
+ DECLARE
+ pgt oid;
+ BEGIN
+ -- We create the function in pg_temp, since it's not certain
+ -- we've got write access anywhere else. This means the call
+ -- needs to be fully-qualified, since functions are never
+ -- looked up in pg_temp. If pg_temp doesn't exist yet, we
+ -- need to autoviv it by creating and dropping a temp table.
+
+ pgt := pg_my_temp_schema();
+
+ IF pgt = 0 THEN
+ CREATE TEMP TABLE "_do autoviv pg_temp" ();
+ END IF;
+
+ PERFORM 1 FROM pg_proc
+ WHERE proname = '_do anon block'
+ AND pronamespace = pg_my_temp_schema();
+ IF FOUND THEN
+ RAISE 'recursive _do not supported';
+ END IF;
+
+ EXECUTE ordered1.do_substs(
+ array[ '$sql$', pg_catalog.quote_literal(sql) ],
+ $do$
+ CREATE FUNCTION pg_temp."_do anon block" () RETURNS void
+ LANGUAGE plpgsql VOLATILE
+ AS $sql$;
+ $do$
+ );
+ PERFORM pg_temp."_do anon block"();
+ DROP FUNCTION pg_temp."_do anon block" ();
+
+ -- we'll keep the temp table around until after we've
+ -- finished with pg_temp, just in case Pg ever decides to
+ -- start dropping it when it isn't needed any more.
+ IF pgt = 0 THEN
+ DROP TABLE "_do autoviv pg_temp";
+ END IF;
+ END;
+ $fn$;

No commit comments for this range

Something went wrong with that request. Please try again.