SlideShare a Scribd company logo
Building and Distributing
     PostgreSQL Extensions
      Without Learning C
                                               David E. Wheeler
                                            PostgreSQL Experts, Inc.

                                                 PGXPUG PgDay 2011




Text: Attribution-Noncommercial-Share Alike 3.0 United States:
http://creativecommons.org/licenses/by-nc-sa/3.0/us/
Images licensed independently and © Their respective owners.
Building and Distributing PostgreSQL Extensions Without Learning C
There are
quite a few
PostgreSQL
Extensions.
Core Extensions
Core Extensions
intagg             pgcrypto

intarray           btree_gin

hstore             chkpass

citext             dblink

cube               earthdistance

isn                fuzzystrmatch
Common Extensions
Common Extensions
PostGIS      PL/R

temporal     OracleFCE

pgmemcache   mysqlcompat

PL/Proxy     pg_randomness

PL/Java      pgTAP

PL/Ruby
Building and Distributing PostgreSQL Extensions Without Learning C
You can create
extensions, too!
Building and Distributing PostgreSQL Extensions Without Learning C
I know what
you’re
thinking…
Building and Distributing PostgreSQL Extensions Without Learning C
“Sure, but I
 get C sick.”
Building and Distributing PostgreSQL Extensions Without Learning C
Fortunately…
Building and Distributing PostgreSQL Extensions Without Learning C
There are
many ways
to extend
PostgreSQL.
Functions




truth.sql
Functions
CREATE
FUNCTION
truthy(




text
)
RETURNS
BOOLEAN
IMMUTABLE
LANGUAGE
SQL
AS

$$




SELECT
$1
IS
NOT
NULL







AND
$1
NOT
IN
('',
'0')







AND
NOT
$1
~
'^[0.]+$';
$$;




 truth.sql
Functions
CREATE
FUNCTION
truthy(




text
)
RETURNS
BOOLEAN
IMMUTABLE
LANGUAGE
SQL
AS

$$




SELECT
$1
IS
NOT
NULL







AND
$1
NOT
IN
('',
'0')







AND
NOT
$1
~
'^[0.]+$';
$$;




 truth.sql
Functions
CREATE
FUNCTION
truthy(




text
)
RETURNS
BOOLEAN
IMMUTABLE
LANGUAGE
SQL
AS

$$




SELECT
$1
IS
NOT
NULL







AND
$1
NOT
IN
('',
'0')







AND
NOT
$1
~
'^[0.]+$';
$$;




 truth.sql
Functions
CREATE
FUNCTION
truthy(




text
)
RETURNS
BOOLEAN
IMMUTABLE
LANGUAGE
SQL
AS

$$




SELECT
$1
IS
NOT
NULL







AND
$1
NOT
IN
('',
'0')







AND
NOT
$1
~
'^[0.]+$';
$$;




 truth.sql
Functions
CREATE
FUNCTION
truthy(




text
)
RETURNS
BOOLEAN
IMMUTABLE
LANGUAGE
SQL
AS

$$




SELECT
$1
IS
NOT
NULL







AND
$1
NOT
IN
('',
'0')







AND
NOT
$1
~
'^[0.]+$';
$$;




 truth.sql
Functions
CREATE
FUNCTION
truthy(




text
)
RETURNS
BOOLEAN
IMMUTABLE
LANGUAGE
SQL
AS

$$




SELECT
$1
IS
NOT
NULL







AND
$1
NOT
IN
('',
'0')







AND
NOT
$1
~
'^[0.]+$';
$$;




 truth.sql
             Might be handy!
Domains




timezone.sql
Domains
CREATE
OR
REPLACE
FUNCTION
is_timezone(




tz
CITEXT
)
RETURNS
BOOLEAN
LANGUAGE
plpgsql
STABLE
AS
$$
BEGIN




PERFORM
NOW()
AT
TIME
ZONE
tz;




RETURN
TRUE;
EXCEPTION
WHEN
invalid_parameter_value
THEN




RETURN
FALSE;
END;
$$;

CREATE
DOMAIN
timezone
AS
CITEXT

CHECK
(
is_timezone(
VALUE
)
);
 timezone.sql
Domains
CREATE
OR
REPLACE
FUNCTION
is_timezone(




tz
CITEXT
)
RETURNS
BOOLEAN
LANGUAGE
plpgsql
STABLE
AS
$$
BEGIN




PERFORM
NOW()
AT
TIME
ZONE
tz;




RETURN
TRUE;
EXCEPTION
WHEN
invalid_parameter_value
THEN




RETURN
FALSE;
END;
$$;

CREATE
DOMAIN
timezone
AS
CITEXT

CHECK
(
is_timezone(
VALUE
)
);
 timezone.sql
Domains
CREATE
OR
REPLACE
FUNCTION
is_timezone(




tz
CITEXT
)
RETURNS
BOOLEAN
LANGUAGE
plpgsql
STABLE
AS
$$
BEGIN




PERFORM
NOW()
AT
TIME
ZONE
tz;




RETURN
TRUE;
EXCEPTION
WHEN
invalid_parameter_value
THEN




RETURN
FALSE;
END;
$$;

CREATE
DOMAIN
timezone
AS
CITEXT

CHECK
(
is_timezone(
VALUE
)
);
 timezone.sql
Domains
CREATE
OR
REPLACE
FUNCTION
is_timezone(




tz
CITEXT
)
RETURNS
BOOLEAN
LANGUAGE
plpgsql
STABLE
AS
$$
BEGIN




PERFORM
NOW()
AT
TIME
ZONE
tz;




RETURN
TRUE;
EXCEPTION
WHEN
invalid_parameter_value
THEN




RETURN
FALSE;
END;
$$;

CREATE
DOMAIN
timezone
AS
CITEXT

CHECK
(
is_timezone(
VALUE
)
);
 timezone.sql
Domains
CREATE
OR
REPLACE
FUNCTION
is_timezone(




tz
CITEXT
)
RETURNS
BOOLEAN
LANGUAGE
plpgsql
STABLE
AS
$$
BEGIN




PERFORM
NOW()
AT
TIME
ZONE
tz;




RETURN
TRUE;
EXCEPTION
WHEN
invalid_parameter_value
THEN




RETURN
FALSE;
END;
$$;

CREATE
DOMAIN
timezone
AS
CITEXT

CHECK
(
is_timezone(
VALUE
)
);
 timezone.sql
Domains
CREATE
OR
REPLACE
FUNCTION
is_timezone(




tz
CITEXT
)
RETURNS
BOOLEAN
LANGUAGE
plpgsql
STABLE
AS
$$
BEGIN




PERFORM
NOW()
AT
TIME
ZONE
tz;




RETURN
TRUE;
EXCEPTION
WHEN
invalid_parameter_value
THEN




RETURN
FALSE;
END;
$$;

CREATE
DOMAIN
timezone
AS
CITEXT

CHECK
(
is_timezone(
VALUE
)
);
 timezone.sql
Domains
CREATE
OR
REPLACE
FUNCTION
is_timezone(




tz
CITEXT
)
RETURNS
BOOLEAN
LANGUAGE
plpgsql
STABLE
AS
$$
BEGIN




PERFORM
NOW()
AT
TIME
ZONE
tz;




RETURN
TRUE;
EXCEPTION
WHEN
invalid_parameter_value
THEN




RETURN
FALSE;
END;
$$;

CREATE
DOMAIN
timezone
AS
CITEXT

CHECK
(
is_timezone(
VALUE
)
);
 timezone.sql
Domains
CREATE
OR
REPLACE
FUNCTION
is_timezone(




tz
CITEXT
)
RETURNS
BOOLEAN
LANGUAGE
plpgsql
STABLE
AS
$$
BEGIN




PERFORM
NOW()
AT
TIME
ZONE
tz;




RETURN
TRUE;
EXCEPTION
WHEN
invalid_parameter_value
THEN




RETURN
FALSE;
END;
$$;                      I use this
CREATE
DOMAIN
timezone
AS
CITEXT

CHECK
(
is_timezone(
VALUE
)
);
 timezone.sql
Types




pair.sql
Types
CREATE
TYPE
pair
AS
(
k
text,
v
text
);

CREATE
OR
REPLACE
FUNCTION
store(




params
variadic
pair[]
)
RETURNS
VOID
LANGUAGE
plpgsql
AS
$$
DECLARE




param
pair;
BEGIN




FOR
param
IN
SELECT
*
FROM
unnest(params)
LOOP








‐‐
...




END
LOOP;
END;
$$

 pair.sql
Types
CREATE
TYPE
pair
AS
(
k
text,
v
text
);

CREATE
OR
REPLACE
FUNCTION
store(




params
variadic
pair[]
)
RETURNS
VOID
LANGUAGE
plpgsql
AS
$$
DECLARE




param
pair;
BEGIN




FOR
param
IN
SELECT
*
FROM
unnest(params)
LOOP








‐‐
...




END
LOOP;
END;
$$

 pair.sql
Types
CREATE
TYPE
pair
AS
(
k
text,
v
text
);

CREATE
OR
REPLACE
FUNCTION
store(




params
variadic
pair[]
)
RETURNS
VOID
LANGUAGE
plpgsql
AS
$$
DECLARE




param
pair;
BEGIN




FOR
param
IN
SELECT
*
FROM
unnest(params)
LOOP








‐‐
...




END
LOOP;
END;
$$

 pair.sql
Types
CREATE
TYPE
pair
AS
(
k
text,
v
text
);

CREATE
OR
REPLACE
FUNCTION
store(




params
variadic
pair[]
)
RETURNS
VOID
LANGUAGE
plpgsql
AS
$$
DECLARE




param
pair;
BEGIN




FOR
param
IN
SELECT
*
FROM
unnest(params)
LOOP








‐‐
...




END
LOOP;
END;
$$

 pair.sql
Types
CREATE
TYPE
pair
AS
(
k
text,
v
text
);

CREATE
OR
REPLACE
FUNCTION
store(




params
variadic
pair[]
)
RETURNS
VOID
LANGUAGE
plpgsql
AS
$$
DECLARE




param
pair;
BEGIN




FOR
param
IN
SELECT
*
FROM
unnest(params)
LOOP








‐‐
...




END
LOOP;
END;
$$

 pair.sql
            Could be useful…
Operators




truthy.sql
Operators
CREATE
OPERATOR
@
(

 RIGHTARG

=
text,

 PROCEDURE
=
truthy
);




 truthy.sql
Operators
CREATE
OPERATOR
@
(

 RIGHTARG

=
text,

 PROCEDURE
=
truthy
);




 truthy.sql
Operators
CREATE
OPERATOR
@
(

 RIGHTARG

=
text,

 PROCEDURE
=
truthy
);




 truthy.sql
Operators
CREATE
OPERATOR
@
(

 RIGHTARG

=
text,

 PROCEDURE
=
truthy
);

SELECT
@'foo';
‐‐
true
SELECT
@'';



‐‐
false




 truthy.sql
Operators
CREATE
OPERATOR
@
(

 RIGHTARG

=
text,

 PROCEDURE
=
truthy
);

SELECT
@'foo';
‐‐
true
SELECT
@'';



‐‐
false




 truthy.sql
              I could use this!
Building and Distributing PostgreSQL Extensions Without Learning C
PostgreSQL
is not merely
a database…
Building and Distributing PostgreSQL Extensions Without Learning C
…It’s an
application
development
platform.
Building and Distributing PostgreSQL Extensions Without Learning C
Develop
reusable
extension
libraries…
Building and Distributing PostgreSQL Extensions Without Learning C
And combine
them when
building apps.
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Let’s create an
extension and
distribute it.
The Steps
The Steps
Stub source, test, and doc files
The Steps
Stub source, test, and doc files

Create the Makefile
The Steps
Stub source, test, and doc files

Create the Makefile

Write the tests
The Steps
Stub source, test, and doc files

Create the Makefile

Write the tests

Write the code
The Steps
Stub source, test, and doc files

Create the Makefile

Write the tests

Write the code

Write the docs
The Steps
Stub source, test, and doc files

Create the Makefile

Write the tests

Write the code

Write the docs

Create the metadata file
The Steps
Stub source, test, and doc files

Create the Makefile

Write the tests

Write the code

Write the docs

Create the metadata file

Package it up
The Steps
Stub source, test, and doc files

Create the Makefile

Write the tests

Write the code

Write the docs

Create the metadata file

Package it up

Release
Stub the Source




sql/pair.sql
Stub the Source
SET
client_min_messages
=
warning;

‐‐
Create
the
type
here.




 sql/pair.sql
Stub the Source
SET
client_min_messages
=
warning;

‐‐
Create
the
type
here.




 sql/pair.sql
Stub the Source
SET
client_min_messages
=
warning;

‐‐
Create
the
type
here.




 sql/pair.sql
Stub the Source
SET
client_min_messages
=
warning;

‐‐
Create
the
type
here.




                    sql/pair.sql
 sql/pair.sql
Stub the Tests




test/sql/base.sql
Stub the Tests
BEGIN;

set
ECHO
0
set
QUIET
1
i
sql/pair.sql
set
QUIET
0

ROLLBACK;




 test/sql/base.sql
Stub the Tests
BEGIN;

set
ECHO
0
set
QUIET
1
i
sql/pair.sql
set
QUIET
0

ROLLBACK;




 test/sql/base.sql
Stub the Tests
BEGIN;

set
ECHO
0
set
QUIET
1
i
sql/pair.sql
set
QUIET
0

ROLLBACK;




     test/sql/base.sql
 test/sql/base.sql
Stub the Docs




test/sql/base.sql
Stub the Docs
pair
0.1.0
==========

Synopsis
‐‐‐‐‐‐‐‐





‐‐
Example
code
here.





Description
‐‐‐‐‐‐‐‐‐‐‐

This
library
blah
blah
blah...

Usage
‐‐‐‐‐

Here's
how
to
use
this
library.

Author
‐‐‐‐‐‐

[David
E.
Wheeler](http://justatheory.com/)

Copyright
‐‐‐‐‐‐‐‐‐

Copyright
(c)
2010
David
E.
Wheeler.


   test/sql/base.sql
Stub the Docs
pair
0.1.0




                                  doc/pair.txt
==========

Synopsis
‐‐‐‐‐‐‐‐





‐‐
Example
code
here.





Description
‐‐‐‐‐‐‐‐‐‐‐

This
library
blah
blah
blah...

Usage
‐‐‐‐‐

Here's
how
to
use
this
library.

Author
‐‐‐‐‐‐

[David
E.
Wheeler](http://justatheory.com/)

Copyright
‐‐‐‐‐‐‐‐‐

Copyright
(c)
2010
David
E.
Wheeler.


   test/sql/base.sql
Create the Makefile




Makefile
Create the Makefile
DATA



=
$(wildcard
sql/*.sql)
DOCS



=
$(wildcard
doc/*.*)
MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*.c))




 Makefile
Create the Makefile
DATA



=
$(wildcard
sql/*.sql)
DOCS



=
$(wildcard
doc/*.*)
MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*.c))




 Makefile
Create the Makefile
DATA



=
$(wildcard
sql/*.sql)
DOCS



=
$(wildcard
doc/*.*)
MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*.c))




 Makefile
Create the Makefile
DATA



=
$(wildcard
sql/*.sql)
DOCS



=
$(wildcard
doc/*.*)
MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*.c))




 Makefile
Create the Makefile
DATA



=
$(wildcard
sql/*.sql)
DOCS



=
$(wildcard
doc/*.*)
MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*.c))

TESTS
=
$(wildcard
test/sql/*.sql)
REGRESS
=
$(patsubst
test/sql/%.sql,%,$(TESTS))
REGRESS_OPTS
=
‐‐inputdir=test




 Makefile
Create the Makefile
DATA



=
$(wildcard
sql/*.sql)
DOCS



=
$(wildcard
doc/*.*)
MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*.c))

TESTS
=
$(wildcard
test/sql/*.sql)
REGRESS
=
$(patsubst
test/sql/%.sql,%,$(TESTS))
REGRESS_OPTS
=
‐‐inputdir=test




 Makefile
Create the Makefile
DATA



=
$(wildcard
sql/*.sql)
DOCS



=
$(wildcard
doc/*.*)
MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*.c))

TESTS
=
$(wildcard
test/sql/*.sql)
REGRESS
=
$(patsubst
test/sql/%.sql,%,$(TESTS))
REGRESS_OPTS
=
‐‐inputdir=test




 Makefile
Create the Makefile
DATA



=
$(wildcard
sql/*.sql)
DOCS



=
$(wildcard
doc/*.*)
MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*.c))

TESTS
=
$(wildcard
test/sql/*.sql)
REGRESS
=
$(patsubst
test/sql/%.sql,%,$(TESTS))
REGRESS_OPTS
=
‐‐inputdir=test




 Makefile
Create the Makefile

                   inputdir
DATA



=
$(wildcard
sql/*.sql)
DOCS



=
$(wildcard
doc/*.*)
MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*.c))

TESTS
=
$(wildcard
test/sql/*.sql)
REGRESS
=
$(patsubst
test/sql/%.sql,%,$(TESTS))
REGRESS_OPTS
=
‐‐inputdir=test




 Makefile
Create the Makefile
DATA



=
$(wildcard
sql/*.sql)
DOCS



=
$(wildcard
doc/*.*)
MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*.c))

TESTS
=
$(wildcard
test/sql/*.sql)
REGRESS
=
$(patsubst
test/sql/%.sql,%,$(TESTS))
REGRESS_OPTS
=
‐‐inputdir=test

PG_CONFIG
=
pg_config
PGXS
:=
$(shell
$(PG_CONFIG)
‐‐pgxs)
include
$(PGXS)




 Makefile
Create the Makefile
DATA



=
$(wildcard
sql/*.sql)
DOCS



=
$(wildcard
doc/*.*)
MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*.c))

TESTS
=
$(wildcard
test/sql/*.sql)
REGRESS
=
$(patsubst
test/sql/%.sql,%,$(TESTS))
REGRESS_OPTS
=
‐‐inputdir=test

PG_CONFIG
=
pg_config
PGXS
:=
$(shell
$(PG_CONFIG)
‐‐pgxs)
include
$(PGXS)




 Makefile
Create the Makefile
DATA



=
$(wildcard
sql/*.sql)
DOCS



=
$(wildcard
doc/*.*)
MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*.c))

TESTS
=
$(wildcard
test/sql/*.sql)
REGRESS
=
$(patsubst
test/sql/%.sql,%,$(TESTS))
REGRESS_OPTS
=
‐‐inputdir=test

PG_CONFIG
=
pg_config
PGXS
:=
$(shell
$(PG_CONFIG)
‐‐pgxs)
include
$(PGXS)




 Makefile
Create the Makefile
DATA



=
$(wildcard
sql/*.sql)
DOCS



=
$(wildcard
doc/*.*)
MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*.c))

TESTS
=
$(wildcard
test/sql/*.sql)
REGRESS
=
$(patsubst
test/sql/%.sql,%,$(TESTS))
REGRESS_OPTS
=
‐‐inputdir=test

PG_CONFIG
=
pg_config
PGXS
:=
$(shell
$(PG_CONFIG)
‐‐pgxs)
include
$(PGXS)




 Makefile
Give it a Try
%

Give it a Try


make
%

make:
Nothing
to
be
done
for
`all'.
%
Give it a Try


make
%

make:
Nothing
to
be
done
for
`all'.


make
installcheck
%
pg_regress
‐‐psqldir=/pgsql/bin
‐‐inputdir=test
base
===
dropping
database
"regression"

===
DROP
DATABASE
===
creating
database
"regression"

===
CREATE
DATABASE
ALTER
DATABASE
===
running
regression
test
queries
===
test
base
















...
diff:


test/expected/base.out:
No
such
file
or
directory
diff
command
failed
with
status
512:


diff

"test/expected/base.out"
"results/base.out"
>

"results/base.out.diff"
make:
***
[installcheck]
Error
2
%
Give it a Try


make
%

make:
Nothing
to
be
done
for
`all'.


make
installcheck
%
pg_regress
‐‐psqldir=/pgsql/bin
‐‐inputdir=test
base
===
dropping
database
"regression"

===
DROP
DATABASE
===
creating
database
"regression"

===
CREATE
DATABASE
ALTER
DATABASE
===
running
regression
test
queries
===
test
base
















...
diff:


test/expected/base.out:
No
such
file
or
directory
diff
command
failed
with
status
512:


diff

"test/expected/base.out"
"results/base.out"
>

"results/base.out.diff"
make:
***
[installcheck]
Error
2
%
Give it a Try


make
%

make:
Nothing
to
be
done
for
`all'.


make
installcheck
%
pg_regress
‐‐psqldir=/pgsql/bin
‐‐inputdir=test
base
===
dropping
database
"regression"

===
DROP
DATABASE
===
creating
database
"regression"

===
                                             We’ll come
CREATE
DATABASE
ALTER
DATABASE
                                            back to that.
===
running
regression
test
queries
===
test
base
















...
diff:


test/expected/base.out:
No
such
file
or
directory
diff
command
failed
with
status
512:


diff

"test/expected/base.out"
"results/base.out"
>

"results/base.out.diff"
make:
***
[installcheck]
Error
2
%
Write Some Tests
i
sql/pair.sql
set
QUIET
0

ROLLBACK;




 test/sql/base.sql
Write Some Tests
i
sql/pair.sql
set
QUIET
0







SELECT
pair('foo',
'bar')
UNION
SELECT
pair('HEY'::text,
'bar')
UNION
SELECT
pair('foo'::text,
'woah'::text)
UNION
SELECT
pair('ick',
'foo'::text)
UNION
SELECT
pair('foo'::text,
1)
UNION
SELECT
pair(12.3,
'foo'::text)
UNION
SELECT
pair(1,
12)
ORDER
BY
pair;

ROLLBACK;

 test/sql/base.sql
Write Some Tests
i
sql/pair.sql
set
QUIET
0







SELECT
pair('foo',
'bar')
UNION
SELECT
pair('HEY'::text,
'bar')
UNION
SELECT
pair('foo'::text,
'woah'::text)
UNION
SELECT
pair('ick',
'foo'::text)
UNION
SELECT
pair('foo'::text,
1)
UNION
SELECT
pair(12.3,
'foo'::text)
UNION
SELECT
pair(1,
12)
ORDER
BY
pair;

ROLLBACK;

 test/sql/base.sql
Write Some Tests
i
sql/pair.sql
set
QUIET
0







SELECT
pair('foo',
'bar')
UNION
SELECT
pair('HEY'::text,
'bar')
UNION
SELECT
pair('foo'::text,
'woah'::text)
UNION
SELECT
pair('ick',
'foo'::text)
UNION
SELECT
pair('foo'::text,
1)
UNION
SELECT
pair(12.3,
'foo'::text)
UNION
SELECT
pair(1,
12)
ORDER
BY
pair;

ROLLBACK;

 test/sql/base.sql
Write Some Tests
i
sql/pair.sql
set
QUIET
0







SELECT
pair('foo',
'bar')
UNION
SELECT
pair('HEY'::text,
'bar')
UNION
SELECT
pair('foo'::text,
'woah'::text)
UNION
SELECT
pair('ick',
'foo'::text)
UNION
SELECT
pair('foo'::text,
1)
UNION
SELECT
pair(12.3,
'foo'::text)
UNION
SELECT
pair(1,
12)
ORDER
BY
pair;

ROLLBACK;

 test/sql/base.sql
Run Them
%

Run Them
%



psql
‐d
try
‐Xf
test/sql/base.sql
BEGIN
psql:test/sql/base.sql:15:
ERROR:

function
pair(unknown,

unknown)
does
not
exist
LINE
1:
SELECT
pair('foo',
'bar')















^
ROLLBACK
%
Run Them
%



psql
‐d
try
‐Xf
test/sql/base.sql
BEGIN
psql:test/sql/base.sql:15:
ERROR:

function
pair(unknown,

unknown)
does
not
exist
LINE
1:
SELECT
pair('foo',
'bar')















^
ROLLBACK
%
                Guess we
             should write it.
SET
client_min_messages
=
warning;

‐‐
Create
the
type
here.




  sql/pair.sql
SET
client_min_messages
=
warning;

CREATE
TYPE
pair
AS
(
k
text,
v
text
);

CREATE
OR
REPLACE
FUNCTION
pair(anyelement,
text)
RETURNS
pair
LANGUAGE
SQL
AS
'SELECT
ROW($1,
$2)::pair';

CREATE
OR
REPLACE
FUNCTION
pair(text,
anyelement)
RETURNS
pair
LANGUAGE
SQL
AS
'SELECT
ROW($1,
$2)::pair';

CREATE
OR
REPLACE
FUNCTION
pair(anyelement,
anyelement)
RETURNS
pair
LANGUAGE
SQL
AS
'SELECT
ROW($1,
$2)::pair';

CREATE
OR
REPLACE
FUNCTION
pair(text,
text)
RETURNS
pair
LANGUAGE
SQL
AS
'SELECT
ROW($1,
$2)::pair;';




  sql/pair.sql
SET
client_min_messages
=
warning;

CREATE
TYPE
pair
AS
(
k
text,
v
text
);

CREATE
OR
REPLACE
FUNCTION
pair(anyelement,
text)
RETURNS
pair
LANGUAGE
SQL
AS
'SELECT
ROW($1,
$2)::pair';

CREATE
OR
REPLACE
FUNCTION
pair(text,
anyelement)
RETURNS
pair
LANGUAGE
SQL
AS
'SELECT
ROW($1,
$2)::pair';

CREATE
OR
REPLACE
FUNCTION
pair(anyelement,
anyelement)
RETURNS
pair
LANGUAGE
SQL
AS
'SELECT
ROW($1,
$2)::pair';

CREATE
OR
REPLACE
FUNCTION
pair(text,
text)
RETURNS
pair
LANGUAGE
SQL
AS
'SELECT
ROW($1,
$2)::pair;';




  sql/pair.sql
SET
client_min_messages
=
warning;

CREATE
TYPE
pair
AS
(
k
text,
v
text
);

CREATE
OR
REPLACE
FUNCTION
pair(anyelement,
text)
RETURNS
pair
LANGUAGE
SQL
AS
'SELECT
ROW($1,
$2)::pair';

CREATE
OR
REPLACE
FUNCTION
pair(text,
anyelement)
RETURNS
pair
LANGUAGE
SQL
AS
'SELECT
ROW($1,
$2)::pair';

CREATE
OR
REPLACE
FUNCTION
pair(anyelement,
anyelement)
RETURNS
pair
LANGUAGE
SQL
AS
'SELECT
ROW($1,
$2)::pair';

CREATE
OR
REPLACE
FUNCTION
pair(text,
text)
RETURNS
pair
LANGUAGE
SQL
AS
'SELECT
ROW($1,
$2)::pair;';




  sql/pair.sql
SET
client_min_messages
=
warning;

CREATE
TYPE
pair
AS
(
k
text,
v
text
);

CREATE
OR
REPLACE
FUNCTION
pair(anyelement,
text)
RETURNS
pair
LANGUAGE
SQL
AS
'SELECT
ROW($1,
$2)::pair';

CREATE
OR
REPLACE
FUNCTION
pair(text,
anyelement)
RETURNS
pair
LANGUAGE
SQL
AS
'SELECT
ROW($1,
$2)::pair';

CREATE
OR
REPLACE
FUNCTION
pair(anyelement,
anyelement)
RETURNS
pair
LANGUAGE
SQL
AS
'SELECT
ROW($1,
$2)::pair';

CREATE
OR
REPLACE
FUNCTION
pair(text,
text)
RETURNS
pair
LANGUAGE
SQL
AS
'SELECT
ROW($1,
$2)::pair;';




  sql/pair.sql
SET
client_min_messages
=
warning;

CREATE
TYPE
pair
AS
(
k
text,
v
text
);

CREATE
OR
REPLACE
FUNCTION
pair(anyelement,
text)
RETURNS
pair
LANGUAGE
SQL
AS
'SELECT
ROW($1,
$2)::pair';

CREATE
OR
REPLACE
FUNCTION
pair(text,
anyelement)
RETURNS
pair
LANGUAGE
SQL
AS
'SELECT
ROW($1,
$2)::pair';

CREATE
OR
REPLACE
FUNCTION
pair(anyelement,
anyelement)
RETURNS
pair
LANGUAGE
SQL
AS
'SELECT
ROW($1,
$2)::pair';

CREATE
OR
REPLACE
FUNCTION
pair(text,
text)
RETURNS
pair
LANGUAGE
SQL
AS
'SELECT
ROW($1,
$2)::pair;';




  sql/pair.sql
SET
client_min_messages
=
warning;

CREATE
TYPE
pair
AS
(
k
text,
v
text
);

CREATE
OR
REPLACE
FUNCTION
pair(anyelement,
text)
RETURNS
pair
LANGUAGE
SQL
AS
'SELECT
ROW($1,
$2)::pair';

CREATE
OR
REPLACE
FUNCTION
pair(text,
anyelement)
RETURNS
pair
LANGUAGE
SQL
AS
'SELECT
ROW($1,
$2)::pair';

CREATE
OR
REPLACE
FUNCTION
pair(anyelement,
anyelement)
RETURNS
pair
LANGUAGE
SQL
AS
'SELECT
ROW($1,
$2)::pair';

CREATE
OR
REPLACE
FUNCTION
pair(text,
text)
RETURNS
pair
LANGUAGE
SQL
AS
'SELECT
ROW($1,
$2)::pair;';




  sql/pair.sql
%
%


psql
‐d
try
‐Xf
test/sql/base.sql
BEGIN




pair




‐‐‐‐‐‐‐‐‐‐‐‐

(1,12)

(12.3,foo)

(HEY,bar)

(foo,1)

(foo,bar)

(foo,woah)

(ick,foo)
(7
rows)

ROLLBACK
%




                                      W00t!
Moar Tests!
UNION
SELECT
pair(12.3,
'foo'::text)
UNION
SELECT
pair(1,
12)
ORDER
BY
pair;

ROLLBACK;




 test/sql/base.sql
Moar Tests!
UNION
SELECT
pair(12.3,
'foo'::text)
UNION
SELECT
pair(1,
12)
ORDER
BY
pair;







SELECT
'foo'
~>
'bar'
AS
arrowop
UNION
SELECT
'HEY'::text
~>
'bar'
UNION
SELECT
'foo'::text
~>
'woah'::text
UNION
SELECT
'ick'
~>
'foo'::text
UNION
SELECT
'foo'::text
~>
1
UNION
SELECT
12.3
~>
'foo'::text
UNION
SELECT
1
~>
12
ORDER
BY
arrowop;

ROLLBACK;
 test/sql/base.sql
Moar Tests!
UNION
SELECT
pair(12.3,
'foo'::text)
UNION
SELECT
pair(1,
12)
ORDER
BY
pair;







SELECT
'foo'
~>
'bar'
AS
arrowop
UNION
SELECT
'HEY'::text
~>
'bar'
UNION
SELECT
'foo'::text
~>
'woah'::text
UNION
SELECT
'ick'
~>
'foo'::text
UNION
SELECT
'foo'::text
~>
1
UNION
SELECT
12.3
~>
'foo'::text
UNION
SELECT
1
~>
12
ORDER
BY
arrowop;

ROLLBACK;
 test/sql/base.sql
Moar Tests!
UNION
SELECT
pair(12.3,
'foo'::text)
UNION
SELECT
pair(1,
12)
ORDER
BY
pair;







SELECT
'foo'
~>
'bar'
AS
arrowop
UNION
SELECT
'HEY'::text
~>
'bar'
UNION
SELECT
'foo'::text
~>
'woah'::text
UNION
SELECT
'ick'
~>
'foo'::text
UNION
SELECT
'foo'::text
~>
1
UNION
SELECT
12.3
~>
'foo'::text
UNION
SELECT
1
~>
12
ORDER
BY
arrowop;

ROLLBACK;
 test/sql/base.sql
Moar Tests!
UNION
SELECT
pair(12.3,
'foo'::text)
UNION
SELECT
pair(1,
12)
ORDER
BY
pair;







SELECT
'foo'
~>
'bar'
AS
arrowop
UNION
SELECT
'HEY'::text
~>
'bar'
UNION
SELECT
'foo'::text
~>
'woah'::text
UNION
SELECT
'ick'
~>
'foo'::text
UNION
SELECT
'foo'::text
~>
1
UNION
SELECT
12.3
~>
'foo'::text
UNION
SELECT
1
~>
12
ORDER
BY
arrowop;

ROLLBACK;
 test/sql/base.sql
%
%


psql
‐d
try
‐Xf
test/sql/base.sql
BEGIN




pair




‐‐‐‐‐‐‐‐‐‐‐‐

(1,12)

(12.3,foo)

(HEY,bar)

(foo,1)

(foo,bar)

(foo,woah)

(ick,foo)
(7
rows)

psql:test/sql/base.sql:25:
ERROR:

operator
does
not
exist:

unknown
~>
unknown
LINE
1:
SELECT
'foo'
~>
'bar'
AS
arrowop





















^
HINT:

No
operator
matches
the
given
name
and
argument

type(s).
You
might
need
to
add
explicit
type
casts.
ROLLBACK
%

(foo,1)

(foo,bar)
%

(foo,woah)

(ick,foo)
(7
rows)

psql:test/sql/base.sql:25:
ERROR:

operator

unknown
~>
unknown
LINE
1:
SELECT
'foo'
~>
'bar'
AS
arrowop





















^
                      Guess we
HINT:

No
operator
matches
the
given
name
an
                   should write it.
type(s).
You
might
need
to
add
explicit
type
ROLLBACK
%
CREATE
OR
REPLACE
FUNCTION
pair(text,
text)
RETURNS
pair
LANGUAGE
SQL
AS
'SELECT
ROW($1,
$2)::pair;';




  sql/pair.sql
CREATE
OR
REPLACE
FUNCTION
pair(text,
text)
RETURNS
pair
LANGUAGE
SQL
AS
'SELECT
ROW($1,
$2)::pair;';

CREATE
OPERATOR
~>
(

   LEFTARG


=
text,

   RIGHTARG

=
anyelement,

   PROCEDURE
=
pair
);

CREATE
OPERATOR
~>
(

   LEFTARG


=
anyelement,

   RIGHTARG

=
text,

   PROCEDURE
=
pair
);

CREATE
OPERATOR
~>
(

   LEFTARG


=
anyelement,

   RIGHTARG

=
anyelement,

   PROCEDURE
=
pair
);

CREATE
OPERATOR
~>
(

   LEFTARG


=
text,

   RIGHTARG

=
text,

   PROCEDURE
=
pair
);
  sql/pair.sql
CREATE
OR
REPLACE
FUNCTION
pair(text,
text)
RETURNS
pair
LANGUAGE
SQL
AS
'SELECT
ROW($1,
$2)::pair;';

CREATE
OPERATOR
~>
(

   LEFTARG


=
text,

   RIGHTARG

=
anyelement,

   PROCEDURE
=
pair
);

CREATE
OPERATOR
~>
(

   LEFTARG


=
anyelement,

   RIGHTARG

=
text,

   PROCEDURE
=
pair
);

CREATE
OPERATOR
~>
(

   LEFTARG


=
anyelement,

   RIGHTARG

=
anyelement,

   PROCEDURE
=
pair
);

CREATE
OPERATOR
~>
(

   LEFTARG


=
text,

   RIGHTARG

=
text,

   PROCEDURE
=
pair
);
  sql/pair.sql
CREATE
OR
REPLACE
FUNCTION
pair(text,
text)
RETURNS
pair
LANGUAGE
SQL
AS
'SELECT
ROW($1,
$2)::pair;';

CREATE
OPERATOR
~>
(

   LEFTARG


=
text,

   RIGHTARG

=
anyelement,

   PROCEDURE
=
pair
);

CREATE
OPERATOR
~>
(

   LEFTARG


=
anyelement,

   RIGHTARG

=
text,

   PROCEDURE
=
pair
);

CREATE
OPERATOR
~>
(

   LEFTARG


=
anyelement,

   RIGHTARG

=
anyelement,

   PROCEDURE
=
pair
);

CREATE
OPERATOR
~>
(

   LEFTARG


=
text,

   RIGHTARG

=
text,

   PROCEDURE
=
pair
);
  sql/pair.sql
CREATE
OR
REPLACE
FUNCTION
pair(text,
text)
RETURNS
pair
LANGUAGE
SQL
AS
'SELECT
ROW($1,
$2)::pair;';

CREATE
OPERATOR
~>
(

   LEFTARG


=
text,

   RIGHTARG

=
anyelement,

   PROCEDURE
=
pair
);

CREATE
OPERATOR
~>
(

   LEFTARG


=
anyelement,

   RIGHTARG

=
text,

   PROCEDURE
=
pair
);

CREATE
OPERATOR
~>
(

   LEFTARG


=
anyelement,

   RIGHTARG

=
anyelement,

   PROCEDURE
=
pair
);

CREATE
OPERATOR
~>
(

   LEFTARG


=
text,

   RIGHTARG

=
text,

   PROCEDURE
=
pair
);
  sql/pair.sql
CREATE
OR
REPLACE
FUNCTION
pair(text,
text)
RETURNS
pair
LANGUAGE
SQL
AS
'SELECT
ROW($1,
$2)::pair;';

CREATE
OPERATOR
~>
(

   LEFTARG


=
text,

   RIGHTARG

=
anyelement,

   PROCEDURE
=
pair
);

CREATE
OPERATOR
~>
(

   LEFTARG


=
anyelement,

   RIGHTARG

=
text,

   PROCEDURE
=
pair
);

CREATE
OPERATOR
~>
(

   LEFTARG


=
anyelement,

   RIGHTARG

=
anyelement,

   PROCEDURE
=
pair
);

CREATE
OPERATOR
~>
(

   LEFTARG


=
text,

   RIGHTARG

=
text,

   PROCEDURE
=
pair
);
  sql/pair.sql
%
%


psql
‐d
try
‐Xf
test/sql/base.sql
BEGIN




pair




‐‐‐‐‐‐‐‐‐‐‐‐

(1,12)

(12.3,foo)

(HEY,bar)

(foo,1)

(foo,bar)

(foo,woah)

(ick,foo)
(7
rows)



arrowop



‐‐‐‐‐‐‐‐‐‐‐‐

(1,12)

(12.3,foo)

(HEY,bar)

(foo,1)

(foo,bar)

(foo,woah)

(ick,foo)
(7
rows)

ROLLBACK

(foo,bar)

(foo,woah)
%


(ick,foo)
(7
rows)



arrowop



‐‐‐‐‐‐‐‐‐‐‐‐

(1,12)

(12.3,foo)

(HEY,bar)

(foo,1)

(foo,bar)

(foo,woah)

(ick,foo)
(7
rows)
               Yay!
ROLLBACK
One More Time!
UNION
SELECT
12.3
~>
'foo'::text
UNION
SELECT
1
~>
12
ORDER
BY
arrowop;

ROLLBACK;




 test/sql/base.sql
One More Time!
UNION
SELECT
12.3
~>
'foo'::text
UNION
SELECT
1
~>
12
ORDER
BY
arrowop;

CREATE
TABLE
kv
(




pair
pair
);

INSERT
INTO
kv
VALUES('foo'
~>
'bar'),
(1
~>
2);

SELECT
(pair).k,
(pair).v
FROM
kv
ORDER
BY
(pair).k;

SELECT
(pair('foo',
'bar')).k,
(pair('foo',
'bar')).v;

SELECT
('foo'
~>
'bar').k,
('foo'
~>
'bar').v;
ROLLBACK;
 test/sql/base.sql
One More Time!
UNION
SELECT
12.3
~>
'foo'::text
UNION
SELECT
1
~>
12
ORDER
BY
arrowop;

CREATE
TABLE
kv
(




pair
pair
);

INSERT
INTO
kv
VALUES('foo'
~>
'bar'),
(1
~>
2);

SELECT
(pair).k,
(pair).v
FROM
kv
ORDER
BY
(pair).k;

SELECT
(pair('foo',
'bar')).k,
(pair('foo',
'bar')).v;

SELECT
('foo'
~>
'bar').k,
('foo'
~>
'bar').v;
ROLLBACK;
 test/sql/base.sql
One More Time!
UNION
SELECT
12.3
~>
'foo'::text
UNION
SELECT
1
~>
12
ORDER
BY
arrowop;

CREATE
TABLE
kv
(




pair
pair
);

INSERT
INTO
kv
VALUES('foo'
~>
'bar'),
(1
~>
2);

SELECT
(pair).k,
(pair).v
FROM
kv
ORDER
BY
(pair).k;

SELECT
(pair('foo',
'bar')).k,
(pair('foo',
'bar')).v;

SELECT
('foo'
~>
'bar').k,
('foo'
~>
'bar').v;
ROLLBACK;
 test/sql/base.sql
One More Time!
UNION
SELECT
12.3
~>
'foo'::text
UNION
SELECT
1
~>
12
ORDER
BY
arrowop;

CREATE
TABLE
kv
(




pair
pair
);

INSERT
INTO
kv
VALUES('foo'
~>
'bar'),
(1
~>
2);

SELECT
(pair).k,
(pair).v
FROM
kv
ORDER
BY
(pair).k;

SELECT
(pair('foo',
'bar')).k,
(pair('foo',
'bar')).v;

SELECT
('foo'
~>
'bar').k,
('foo'
~>
'bar').v;
ROLLBACK;
 test/sql/base.sql
One More Time!
UNION
SELECT
12.3
~>
'foo'::text
UNION
SELECT
1
~>
12
ORDER
BY
arrowop;

CREATE
TABLE
kv
(




pair
pair
);

INSERT
INTO
kv
VALUES('foo'
~>
'bar'),
(1
~>
2);

SELECT
(pair).k,
(pair).v
FROM
kv
ORDER
BY
(pair).k;

SELECT
(pair('foo',
'bar')).k,
(pair('foo',
'bar')).v;

SELECT
('foo'
~>
'bar').k,
('foo'
~>
'bar').v;
ROLLBACK;
 test/sql/base.sql
One More Time!
UNION
SELECT
12.3
~>
'foo'::text
UNION
SELECT
1
~>
12
ORDER
BY
arrowop;

CREATE
TABLE
kv
(




pair
pair
);

INSERT
INTO
kv
VALUES('foo'
~>
'bar'),
(1
~>
2);

SELECT
(pair).k,
(pair).v
FROM
kv
ORDER
BY
(pair).k;

SELECT
(pair('foo',
'bar')).k,
(pair('foo',
'bar')).v;

SELECT
('foo'
~>
'bar').k,
('foo'
~>
'bar').v;
ROLLBACK;
 test/sql/base.sql
%
%


psql
‐d
try
‐Xf
test/sql/base.sql
BEGIN




pair




‐‐‐‐‐‐‐‐‐‐‐‐

(1,12)

(12.3,foo)

(HEY,bar)

(foo,1)

(foo,bar)

(foo,woah)

(ick,foo)
(7
rows)



arrowop



‐‐‐‐‐‐‐‐‐‐‐‐

(1,12)

(12.3,foo)

(HEY,bar)

(foo,1)

(foo,bar)

(foo,woah)

(ick,foo)
(7
rows)

CREATE
TABLE
INSERT
0
2


k

|

v


‐‐‐‐‐+‐‐‐‐‐

1


|
2

foo
|
bar
(2
rows)



k

|

v


‐‐‐‐‐+‐‐‐‐‐

foo
|
bar




                              We’re golden!
(1
row)



k

|

v


‐‐‐‐‐+‐‐‐‐‐

foo
|
bar
(1
row)

ROLLBACK
%
Remember This?
%

Remember This?
%



make
installcheck
pg_regress
‐‐psqldir=/pgsql/bin
‐‐inputdir=test
base
===
dropping
database
"regression"

===
DROP
DATABASE
===
creating
database
"regression"

===
CREATE
DATABASE
ALTER
DATABASE
===
running
regression
test
queries
===
test
base
















...
diff:


test/expected/base.out:
No
such
file
or
directory
diff
command
failed
with
status
512:


diff

"test/expected/base.out"
"results/base.out"
>

"results/base.out.diff"
make:
***
[installcheck]
Error
2
%
Let’s Fix That
%

Let’s Fix That
%



echo
'set
ECHO
0'
>
test/expected/base.out

%
Let’s Fix That
%



echo
'set
ECHO
0'
>
test/expected/base.out



psql
‐d
try
‐Xf
test/sql/base.sql

%


|
grep
‐v
^BEGIN
>>
test/expected/base.out
%
Let’s Fix That
%



echo
'set
ECHO
0'
>
test/expected/base.out



psql
‐d
try
‐Xf
test/sql/base.sql

%


|
grep
‐v
^BEGIN
>>
test/expected/base.out


%
make
installcheck
pg_regress
‐‐psqldir=/pgsql/bin
‐‐inputdir=test
base
(using
postmaster
on
Unix
socket,
default
port)
==============
dropping
database
"regression"









==============
DROP
DATABASE
==============
creating
database
"regression"









==============
CREATE
DATABASE
ALTER
DATABASE
==============
running
regression
test
queries








==============
test
base
















...
ok

=====================

All
1
tests
passed.

=====================
Let’s Fix That
%



echo
'set
ECHO
0'
>
test/expected/base.out



psql
‐d
try
‐Xf
test/sql/base.sql

%


|
grep
‐v
^BEGIN
>>
test/expected/base.out


%
make
installcheck
pg_regress
‐‐psqldir=/pgsql/bin
‐‐inputdir=test
base
(using
postmaster
on
Unix
socket,
default
port)
==============
dropping
database
"regression"









==============
DROP
DATABASE
==============
creating
database
"regression"









==============
CREATE
DATABASE
ALTER
DATABASE
==============
running
regression
test
queries








==============
test
base
















...
ok

=====================

All
1
tests
passed.

=====================
                                 Woo hoo!
Where are We?
Stub source, test, and doc files

Create the Makefile

Write the tests

Write the code

Write the docs

Create the metadata file

Package it up

Release
Where are We?
✔   Stub source, test, and doc files

    Create the Makefile

    Write the tests

    Write the code

    Write the docs

    Create the metadata file

    Package it up

    Release
Where are We?
✔   Stub source, test, and doc files

✔   Create the Makefile

    Write the tests

    Write the code

    Write the docs

    Create the metadata file

    Package it up

    Release
Where are We?
✔   Stub source, test, and doc files

✔   Create the Makefile

✔   Write the tests

    Write the code

    Write the docs

    Create the metadata file

    Package it up

    Release
Where are We?
✔   Stub source, test, and doc files

✔   Create the Makefile

✔   Write the tests

✔   Write the code

    Write the docs

    Create the metadata file

    Package it up

    Release
Write the Docs
pair
0.1.0
==========

Synopsis
‐‐‐‐‐‐‐‐





‐‐
Example
code
here.

Description
‐‐‐‐‐‐‐‐‐‐‐

This
library
blah
blah
blah...

Usage
‐‐‐‐‐

Here's
how
to
use
this
library.

Author
‐‐‐‐‐‐

[David
E.
Wheeler](http://justatheory.com/)

Copyright
‐‐‐‐‐‐‐‐‐

Copyright
(c)
2010
David
E.
Wheeler.


   test/sql/base.sql
Write the Docs
pair
0.1.0
==========

Synopsis
‐‐‐‐‐‐‐‐





%
CREATE
EXTENSION
pair;




CREATE
EXTENSION





%
SELECT
pair('foo',
'bar');








pair




‐‐‐‐‐‐‐‐‐‐‐‐





(foo,bar)





%
SELECT
'foo'
~>
'bar';








pair




‐‐‐‐‐‐‐‐‐‐‐‐





(foo,bar)

Description
‐‐‐‐‐‐‐‐‐‐‐

This
library
blah
blah
blah...

Usage
‐‐‐‐‐

Here's
how
to
use
this
library.

Author
   test/sql/base.sql
‐‐‐‐‐‐
Write the Docs
Description
‐‐‐‐‐‐‐‐‐‐‐

This
library
blah
blah
blah...

Usage
‐‐‐‐‐

Here's
how
to
use
this
library.

Author
‐‐‐‐‐‐

[David
E.
Wheeler](http://justatheory.com/)

Copyright
‐‐‐‐‐‐‐‐‐

Copyright
(c)
2010
David
E.
Wheeler.




   test/sql/base.sql
Write the Docs
Description
‐‐‐‐‐‐‐‐‐‐‐

This
library
contains
a
single
PostgreSQL
extension,
a
key/value
pair
data
type
called
`pair`,
along
with
a
convenience
function
for
constructing
key/value
pairs.
It's
just
a
simple
thing,
really:
a
two‐value
composite
type
that
can
store
any
type
of
value
in
its
slots,
which
are
named
`k`
and
`v`.

Usage
‐‐‐‐‐

Here's
how
to
use
this
library.

Author
‐‐‐‐‐‐

[David
E.
Wheeler](http://justatheory.com/)

Copyright
‐‐‐‐‐‐‐‐‐

Copyright
(c)
2010
David
E.
Wheeler.




   test/sql/base.sql
META.json




META.json
META.json
{



"name":






"pair",



"abstract":


"A
key/value
pair
data
type",



"version":



"0.1.0",



"maintainer":
"Tom
Lane
<tgl@postgresql.org>",



"license":



"postgresql",



"provides":
{






"pair":
{









"file":




"pair.sql",









"version":

"0.1.0"






}



},



"meta‐spec":

{






"version":
"1.0.0",






"url":
"http://pgxn.org/meta/spec.txt"



}
}


  META.json
META.json
{



"name":






"pair",



"abstract":


"A
key/value
pair
data
type",



"version":



"0.1.0",



"maintainer":
"Tom
Lane
<tgl@postgresql.org>",



"license":



"postgresql",



"provides":
{






"pair":
{









"file":




"pair.sql",









"version":

"0.1.0"






}



},



"meta‐spec":

{






"version":
"1.0.0",






"url":
"http://pgxn.org/meta/spec.txt"



}
}


  META.json
META.json
{



"name":






"pair",



"abstract":


"A
key/value
pair
data
type",



"version":



"0.1.0",



"maintainer":
"Tom
Lane
<tgl@postgresql.org>",



"license":



"postgresql",



"provides":
{






"pair":
{









"file":




"pair.sql",









"version":

"0.1.0"






}



},



"meta‐spec":

{






"version":
"1.0.0",






"url":
"http://pgxn.org/meta/spec.txt"



}
}


  META.json
META.json
{



"name":






"pair",



"abstract":


"A
key/value
pair
data
type",



"version":



"0.1.0",



"maintainer":
"Tom
Lane
<tgl@postgresql.org>",



"license":



"postgresql",



"provides":
{






"pair":
{









"file":




"pair.sql",









"version":

"0.1.0"






}



},



"meta‐spec":

{






"version":
"1.0.0",






"url":
"http://pgxn.org/meta/spec.txt"



}
}


  META.json
META.json
{                   semver.org



"name":






"pair",



"abstract":


"A
key/value
pair
data
type",



"version":



"0.1.0",



"maintainer":
"Tom
Lane
<tgl@postgresql.org>",



"license":



"postgresql",



"provides":
{






"pair":
{









"file":




"pair.sql",









"version":

"0.1.0"






}



},



"meta‐spec":

{






"version":
"1.0.0",






"url":
"http://pgxn.org/meta/spec.txt"



}
}


  META.json
META.json
{



"name":






"pair",



"abstract":


"A
key/value
pair
data
type",



"version":



"0.1.0",



"maintainer":
"Tom
Lane
<tgl@postgresql.org>",



"license":



"postgresql",



"provides":
{






"pair":
{









"file":




"pair.sql",









"version":

"0.1.0"






}



},



"meta‐spec":

{






"version":
"1.0.0",






"url":
"http://pgxn.org/meta/spec.txt"



}
}


  META.json
META.json
{



"name":






"pair",



"abstract":


"A
key/value
pair
data
type",



"version":



"0.1.0",



"maintainer":
"Tom
Lane
<tgl@postgresql.org>",



"license":



"postgresql",



"provides":
{






"pair":
{









"file":




"pair.sql",









"version":

"0.1.0"






}



},



"meta‐spec":

{






"version":
"1.0.0",






"url":
"http://pgxn.org/meta/spec.txt"



}
}


  META.json
META.json
{



"name":






"pair",



"abstract":


"A
key/value
pair
data
type",



"version":



"0.1.0",



"maintainer":
"Tom
Lane
<tgl@postgresql.org>",



"license":



"postgresql",



"provides":
{






"pair":
{









"file":




"pair.sql",









"version":

"0.1.0"






}



},



"meta‐spec":

{






"version":
"1.0.0",






"url":
"http://pgxn.org/meta/spec.txt"



}
}


  META.json
META.json
{



"name":






"pair",



"abstract":


"A
key/value
pair
data
type",



"version":



"0.1.0",



"maintainer":
"Tom
Lane
<tgl@postgresql.org>",



"license":



"postgresql",



"provides":
{






"pair":
{









"file":




"pair.sql",









"version":

"0.1.0"






}



},



"meta‐spec":

{






"version":
"1.0.0",






"url":
"http://pgxn.org/meta/spec.txt"



}
}


  META.json
META.json
{



"name":






"pair",



"abstract":


"A
key/value
pair
data
type",



"version":



"0.1.0",



"maintainer":
"Tom
Lane
<tgl@postgresql.org>",



"license":



"postgresql",



"provides":
{






"pair":
{









"file":




"pair.sql",









"version":

"0.1.0"






}



},



"meta‐spec":

{






"version":
"1.0.0",






"url":
"http://pgxn.org/meta/spec.txt"



               At least this…



}
}


  META.json
Package it Up!
%

Package it Up!
%



git
archive
‐‐format
zip
‐‐prefix=pair‐0.1.0/



‐‐output
~/Desktop/pair‐0.1.0.zip
master
%
Package it Up!
%



git
archive
‐‐format
zip
‐‐prefix=pair‐0.1.0/



‐‐output
~/Desktop/pair‐0.1.0.zip
master
%




                         Easy, eh?
Where are We?
✔   Stub source, test, and doc files

✔   Create the Makefile

✔   Write the tests

✔   Write the code

    Write the docs

    Create the metadata file

    Package it up

    Release
Where are We?
✔   Stub source, test, and doc files

✔   Create the Makefile

✔   Write the tests

✔   Write the code

✔   Write the docs

    Create the metadata file

    Package it up

    Release
Where are We?
✔   Stub source, test, and doc files

✔   Create the Makefile

✔   Write the tests

✔   Write the code

✔   Write the docs

✔   Create the metadata file

    Package it up

    Release
Where are We?
✔   Stub source, test, and doc files

✔   Create the Makefile

✔   Write the tests

✔   Write the code

✔   Write the docs

✔   Create the metadata file

✔   Package it up

    Release
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Tom Lane

tgl@postgresql.org

http://postgresql.org/~tgl/

tomlane

@tomlane




I’ve got some killer extensions in development that I think
will be useful to everyone, including:

* pair: an ordered pair data type
* PL/Brainfuck: just what it sounds like
Tom Lane

tgl@postgresql.org

http://postgresql.org/~tgl/

tomlane

@tomlane




I’ve got some killer extensions in development that I think
will be useful to everyone, including:

* pair: an ordered pair data type
* PL/Brainfuck: just what it sounds like
tomlane
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
omg WTF ROTFL lolz
omg WTF ROTFL lolz
omg WTF ROTFL lolz
omg WTF ROTFL lolz
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
tomlane

●●●●●●●●●●●●●●●●●●
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
tomlane
tomlane
META.json
{



"name":
"pair",



"abstract":
"A
key/value
pair
data
type",



"description":
"This
library
contains
a
single
PostgreSQL
extension,
a
key/value



"version":
"0.1.0",



"maintainer":
"Tom
Lane
<tgl@postgresql.org>",



"license":
"postgresql",



"provides":
{






"pair":
{









"file":
"sql/pair.sql",









"version":
"0.1.0"






}



},



"resources":
{






"bugtracker":
{









"web":
"http://github.com/tgl/kv‐pair/issues/"






},






"repository":
{








"url":

"git://github.com/tgl/kv‐pair.git",








"web":

"http://github.com/tgl/kv‐pair/",








"type":
"git"






}



},



"generated_by":
"Tom
Lane",



"meta‐spec":
{






"version":
"1.0.0",






"url":
"http://pgxn.org/meta/spec.txt"



},



"tags":
[






"variadic
function",
"ordered
pair",
"pair",
"key
value",
"key
value
pair"



]
}


  META.json
{



"name":
"pair",



"abstract":
"A
key/value
pair
data
type",



"description":
"This
library
contains
a
single
PostgreSQL
extension,
a
key/value



"version":
"0.1.0",



"maintainer":
"Tom
Lane
<tgl@postgresql.org>",



"license":
"postgresql",



"provides":
{






"pair":
{









"file":
"sql/pair.sql",









"version":
"0.1.0"






}



},



"resources":
{






"bugtracker":
{









"web":
"http://github.com/tgl/kv‐pair/issues/"






},






"repository":
{








"url":

"git://github.com/tgl/kv‐pair.git",








"web":

"http://github.com/tgl/kv‐pair/",








"type":
"git"






}



},



"generated_by":
"Tom
Lane",



"meta‐spec":
{






"version":
"1.0.0",






"url":
"http://pgxn.org/meta/spec.txt"



},



"tags":
[






"variadic
function",
"ordered
pair",
"pair",
"key
value",
"key
value
pair"



]
}


  META.json
{



"name":
"pair",



"abstract":
"A
key/value
pair
data
type",



"description":
"This
library
contains
a
single
PostgreSQL
extension,
a
key/value



"version":
"0.1.0",



"maintainer":
"Tom
Lane
<tgl@postgresql.org>",



"license":
"postgresql",



"provides":
{






"pair":
{









"file":
"sql/pair.sql",









"version":
"0.1.0"






}



},



"resources":
{






"bugtracker":
{









"web":
"http://github.com/tgl/kv‐pair/issues/"






},






"repository":
{








"url":

"git://github.com/tgl/kv‐pair.git",








"web":

"http://github.com/tgl/kv‐pair/",








"type":
"git"






}



},



"generated_by":
"Tom
Lane",



"meta‐spec":
{






"version":
"1.0.0",






"url":
"http://pgxn.org/meta/spec.txt"



},



"tags":
[






"variadic
function",
"ordered
pair",
"pair",
"key
value",
"key
value
pair"



]
}


  META.json
{



"name":
"pair",



"abstract":
"A
key/value
pair
data
type",



"description":
"This
library
contains
a
single
PostgreSQL
extension,
a
key/value



"version":
"0.1.0",



"maintainer":
"Tom
Lane
<tgl@postgresql.org>",



"license":
"postgresql",



"provides":
{






"pair":
{









"file":
"sql/pair.sql",









"version":
"0.1.0"






}



},



"resources":
{






"bugtracker":
{









"web":
"http://github.com/tgl/kv‐pair/issues/"






},






"repository":
{








"url":

"git://github.com/tgl/kv‐pair.git",








"web":

"http://github.com/tgl/kv‐pair/",








"type":
"git"






}



},



"generated_by":
"Tom
Lane",



"meta‐spec":
{






"version":
"1.0.0",






"url":
"http://pgxn.org/meta/spec.txt"



},



"tags":
[






"variadic
function",
"ordered
pair",
"pair",
"key
value",
"key
value
pair"



]
}


  META.json
{



"name":
"pair",



"abstract":
"A
key/value
pair
data
type",



"description":
"This
library
contains
a
single
PostgreSQL
extension,
a
key/value



"version":
"0.1.0",



"maintainer":
"Tom
Lane
<tgl@postgresql.org>",



"license":
"postgresql",



"provides":
{






"pair":
{









"file":
"sql/pair.sql",









"version":
"0.1.0"






}



},



"resources":
{






"bugtracker":
{









"web":
"http://github.com/tgl/kv‐pair/issues/"






},






"repository":
{








"url":

"git://github.com/tgl/kv‐pair.git",








"web":

"http://github.com/tgl/kv‐pair/",








"type":
"git"






}



},



"generated_by":
"Tom
Lane",



"meta‐spec":
{






"version":
"1.0.0",






"url":
"http://pgxn.org/meta/spec.txt"



},



"tags":
[






"variadic
function",
"ordered
pair",
"pair",
"key
value",
"key
value
pair"



]
}


  META.json
Where are We?
✔   Stub source, test, and doc files

✔   Create the Makefile

✔   Write the tests

✔   Write the code

✔   Write the docs

✔   Create the metadata file

✔   Package it up

    Release
Where are We?
✔   Stub source, test, and doc files

✔   Create the Makefile

✔   Write the tests

✔   Write the code

✔   Write the docs

✔   Create the metadata file

✔   Package it up

✔   Release
Why PGXN?
Why PGXN?
World-wide network of mirrors
Why PGXN?
World-wide network of mirrors

Fully indexed extensions
Why PGXN?
World-wide network of mirrors

Fully indexed extensions

RESTful metadata distribution
Why PGXN?
World-wide network of mirrors

Fully indexed extensions

RESTful metadata distribution

Search and documentation site
Why PGXN?
World-wide network of mirrors

Fully indexed extensions

RESTful metadata distribution

Search and documentation site

Comprehensive REST API
Why PGXN?
World-wide network of mirrors

Fully indexed extensions

RESTful metadata distribution

Search and documentation site

Comprehensive REST API

Command-line client
Building and Distributing PostgreSQL Extensions Without Learning C
pair
pair
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
PGXN Client
%
PGXN Client
%


sudo
easy_install
pgxnclient
Installing
pgxncli.py
script
to
/usr/local/bin
Installing
pgxn
script
to
/usr/local/bin
Processing
dependencies
for
pgxnclient
Finished
processing
dependencies
for
pgxnclient
%
PGXN Client
%


sudo
easy_install
pgxnclient
Installing
pgxncli.py
script
to
/usr/local/bin
Installing
pgxn
script
to
/usr/local/bin
Processing
dependencies
for
pgxnclient
Finished
processing
dependencies
for
pgxnclient


pgxn
install
pair
%
INFO:
best
version:
pair
0.1.3
INFO:
saving
/tmp/pair‐0.1.3.zip
INFO:
unpacking:
/tmp/pair‐0.1.3.zip
INFO:
building
extension
INFO:
installing
extension
%
PGXN Client
%


sudo
easy_install
pgxnclient
Installing
pgxncli.py
script
to
/usr/local/bin
Installing
pgxn
script
to
/usr/local/bin
Processing
dependencies
for
pgxnclient
Finished
processing
dependencies
for
pgxnclient


pgxn
install
pair
%
INFO:
best
version:
pair
0.1.3
INFO:
saving
/tmp/pair‐0.1.3.zip
INFO:
unpacking:
/tmp/pair‐0.1.3.zip
INFO:
building
extension
INFO:
installing
extension
%
PGXN Client
%


sudo
easy_install
pgxnclient
Installing
pgxncli.py
script
to
/usr/local/bin
Installing
pgxn
script
to
/usr/local/bin
Processing
dependencies
for
pgxnclient
Finished
processing
dependencies
for
pgxnclient


pgxn
install
pair
%
INFO:
best
version:
pair
0.1.3
INFO:
saving
/tmp/pair‐0.1.3.zip
INFO:
unpacking:
/tmp/pair‐0.1.3.zip
INFO:
building
extension
INFO:
installing
extension


%
pgxn
load
pair
‐d
try


INFO:
best
version:
pair
0.1.3
CREATE
TYPE
CREATE
FUNCTION
CREATE
OPERATOR
%
Thank you
Daniele Varrazzo
Coming in 9.1
Coming in 9.1
9.1 adding extension support
Coming in 9.1
9.1 adding extension support
CREATE
EXTENSION
pair
WITH
SCHEMA
utils;
Coming in 9.1
9.1 adding extension support
CREATE
EXTENSION
pair
WITH
SCHEMA
utils;

Encapsulates dump/restore
Coming in 9.1
9.1 adding extension support
CREATE
EXTENSION
pair
WITH
SCHEMA
utils;

Encapsulates dump/restore

Whatever is defined in the SQL script
Coming in 9.1
9.1 adding extension support
CREATE
EXTENSION
pair
WITH
SCHEMA
utils;

Encapsulates dump/restore

Whatever is defined in the SQL script

Supporting it is easy
9.1 Extension Packaging
9.1 Extension Packaging
 Control file
9.1 Extension Packaging
 Control file

 Migration from unpackaged
9.1 Extension Packaging
 Control file

 Migration from unpackaged

   pair--unpackaged--0.1.0.sql
9.1 Extension Packaging
 Control file

 Migration from unpackaged

   pair--unpackaged--0.1.0.sql

 Properly-named SQL script
9.1 Extension Packaging
 Control file

 Migration from unpackaged

   pair--unpackaged--0.1.0.sql

 Properly-named SQL script

   pair--0.1.0.sql
Create the control file




pair.control
Create the control file

#
pair
extension
comment
=
'A
key/value
pair
data
type'
default_version
=
'0.1.0'
module_pathname
=
'$libdir/pair'
relocatable
=
true
superuser
=
false




 pair.control
Create the control file

#
pair
extension
comment
=
'A
key/value
pair
data
type'
default_version
=
'0.1.0'
module_pathname
=
'$libdir/pair'
relocatable
=
true
superuser
=
false




 pair.control
Create the control file
                         Optional
#
pair
extension
comment
=
'A
key/value
pair
data
type'
default_version
=
'0.1.0'
module_pathname
=
'$libdir/pair'
relocatable
=
true
superuser
=
false




 pair.control
Create the control file

#
pair
extension
comment
=
'A
key/value
pair
data
type'
default_version
=
'0.1.0'
module_pathname
=
'$libdir/pair'
relocatable
=
true
superuser
=
false




 pair.control
Create the control file

#
pair
extension
comment
=
'A
key/value
pair
data
type'
default_version
=
'0.1.0'
module_pathname
=
'$libdir/pair'
relocatable
=
true
superuser
=
false




 pair.control
Create the control file

#
pair
extension
                                 For C
comment
=
'A
key/value
pair
data
type'
default_version
=
'0.1.0'        code
module_pathname
=
'$libdir/pair'
relocatable
=
true
superuser
=
false




 pair.control
Create the control file

#
pair
extension
comment
=
'A
key/value
pair
data
type'
default_version
=
'0.1.0'
module_pathname
=
'$libdir/pair'
relocatable
=
true
superuser
=
false




 pair.control
Create the control file

#
pair
extension Can move
comment
=
'A
key/value
pair
data
type'
                 schemas
default_version
=
'0.1.0'
module_pathname
=
'$libdir/pair'
relocatable
=
true
superuser
=
false




 pair.control
Create the control file

#
pair
extension
comment
=
'A
key/value
pair
data
type'
default_version
=
'0.1.0'
module_pathname
=
'$libdir/pair'
relocatable
=
true
superuser
=
false




 pair.control
Create the control file

#
pair
extension
comment
=
'A
key/value
pair
data
type'
default_version
=
'0.1.0'
module_pathname
=
'$libdir/pair'
relocatable
=
true Not required
superuser
=
false
                     to install



 pair.control
Create the control file

#
pair
extension
comment
=
'A
key/value
pair
data
type'
default_version
=
'0.1.0'
module_pathname
=
'$libdir/pair'
relocatable
=
true
superuser
=
false



                pair.control
 pair.control
Migration from
                    Unpackaged




sql/pair‐‐unpac…
Migration from
                      Unpackaged
ALTER
EXTENSION
pair
ADD
TYPE
pair;
ALTER
EXTENSION
pair
ADD
FUNCTION
pair(anyelement,
text);
ALTER
EXTENSION
pair
ADD
FUNCTION
pair(text,
anyelement);
ALTER
EXTENSION
pair
ADD
FUNCTION
pair(anyelement,
anyelement);
ALTER
EXTENSION
pair
ADD
FUNCTION
pair(text,
text);
ALTER
EXTENSION
pair
ADD
OPERATOR
~>(text,
anyelement);
ALTER
EXTENSION
pair
ADD
OPERATOR
~>(anyelement,
text);
ALTER
EXTENSION
pair
ADD
OPERATOR
~>(anyelement,
anyelement);
ALTER
EXTENSION
pair
ADD
OPERATOR
~>(text,
text);




  sql/pair‐‐unpac…
Migration from
                      Unpackaged
ALTER
EXTENSION
pair
ADD
TYPE
pair;
ALTER
EXTENSION
pair
ADD
FUNCTION
pair(anyelement,
text);
ALTER
EXTENSION
pair
ADD
FUNCTION
pair(text,
anyelement);
ALTER
EXTENSION
pair
ADD
FUNCTION
pair(anyelement,
anyelement);
ALTER
EXTENSION
pair
ADD
FUNCTION
pair(text,
text);
ALTER
EXTENSION
pair
ADD
OPERATOR
~>(text,
anyelement);
ALTER
EXTENSION
pair
ADD
OPERATOR
~>(anyelement,
text);
ALTER
EXTENSION
pair
ADD
OPERATOR
~>(anyelement,
anyelement);
ALTER
EXTENSION
pair
ADD
OPERATOR
~>(text,
text);




          sql/pair--unpackaged--0.1.0.sql
  sql/pair‐‐unpac…
Update the Makefile




Makefile
Update the Makefile
EXTENSION



=
pair
EXTVERSION


=
$(shell
grep
default_version



$(EXTENSION).control
|

sed
‐e



"s/default_version[
]*=[
]*'([^']*)'/1/")
DATA








=
$(filter‐out
$(wildcard
sql/*‐‐*.sql),$
(wildcard
sql/*.sql))
DOCS








=
$(wildcard
doc/*.*)
TESTS







=
$(wildcard
test/sql/*.sql)
REGRESS





=
$(patsubst
test/sql/%.sql,%,$(TESTS))
REGRESS_OPTS
=
‐‐inputdir=test
MODULES





=
$(patsubst
%.c,%,$(wildcard
src/*.c))

PG_CONFIG



=
pg_config
PG91

=
$(shell
$(PG_CONFIG)
‐‐version



|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes)

 Makefile
Update the Makefile
EXTENSION



=
pair
EXTVERSION


=
$(shell
grep
default_version



$(EXTENSION).control
|

sed
‐e



"s/default_version[
]*=[
]*'([^']*)'/1/")
DATA








=
$(filter‐out
$(wildcard
sql/*‐‐*.sql),$
(wildcard
sql/*.sql))
DOCS








=
$(wildcard
doc/*.*)
TESTS







=
$(wildcard
test/sql/*.sql)
REGRESS





=
$(patsubst
test/sql/%.sql,%,$(TESTS))
REGRESS_OPTS
=
‐‐inputdir=test
MODULES





=
$(patsubst
%.c,%,$(wildcard
src/*.c))

PG_CONFIG



=
pg_config
PG91

=
$(shell
$(PG_CONFIG)
‐‐version



|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes)

 Makefile
Update the Makefile
EXTENSION



=
pair
EXTVERSION


=
$(shell
grep
default_version



$(EXTENSION).control
|

sed
‐e



"s/default_version[
]*=[
]*'([^']*)'/1/")
DATA








=
$(filter‐out
$(wildcard
sql/*‐‐*.sql),$
(wildcard
sql/*.sql))
DOCS








=
$(wildcard
doc/*.*)
TESTS







=
$(wildcard
test/sql/*.sql)
REGRESS





=
$(patsubst
test/sql/%.sql,%,$(TESTS))
REGRESS_OPTS
=
‐‐inputdir=test
MODULES





=
$(patsubst
%.c,%,$(wildcard
src/*.c))

PG_CONFIG



=
pg_config
PG91

=
$(shell
$(PG_CONFIG)
‐‐version



|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes)

 Makefile
Update the Makefile
                                               Extract from
EXTENSION



=
pair
EXTVERSION


=
$(shell
grep
default_version
   control file


$(EXTENSION).control
|

sed
‐e



"s/default_version[
]*=[
]*'([^']*)'/1/")
DATA








=
$(filter‐out
$(wildcard
sql/*‐‐*.sql),$
(wildcard
sql/*.sql))
DOCS








=
$(wildcard
doc/*.*)
TESTS







=
$(wildcard
test/sql/*.sql)
REGRESS





=
$(patsubst
test/sql/%.sql,%,$(TESTS))
REGRESS_OPTS
=
‐‐inputdir=test
MODULES





=
$(patsubst
%.c,%,$(wildcard
src/*.c))

PG_CONFIG



=
pg_config
PG91

=
$(shell
$(PG_CONFIG)
‐‐version



|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes)

 Makefile
Update the Makefile
EXTENSION



=
pair
EXTVERSION


=
$(shell
grep
default_version



$(EXTENSION).control
|

sed
‐e



"s/default_version[
]*=[
]*'([^']*)'/1/")
DATA








=
$(filter‐out
$(wildcard
sql/*‐‐*.sql),$
(wildcard
sql/*.sql))
DOCS








=
$(wildcard
doc/*.*)
TESTS







=
$(wildcard
test/sql/*.sql)
REGRESS





=
$(patsubst
test/sql/%.sql,%,$(TESTS))
REGRESS_OPTS
=
‐‐inputdir=test
MODULES





=
$(patsubst
%.c,%,$(wildcard
src/*.c))

PG_CONFIG



=
pg_config
PG91

=
$(shell
$(PG_CONFIG)
‐‐version



|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes)

 Makefile
Update the Makefile
EXTENSION



=
pair
EXTVERSION


=
$(shell
grep
default_version



$(EXTENSION).control
|

sed
‐e



"s/default_version[
]*=[
]*'([^']*)'/1/")
DATA








=
$(filter‐out
$(wildcard
sql/*‐‐*.sql),$
(wildcard
sql/*.sql))
DOCS








=
$(wildcard
doc/*.*)
TESTS







=
$(wildcard
test/sql/*.sql)
REGRESS





=
$(patsubst
test/sql/%.sql,%,$(TESTS))
REGRESS_OPTS
=
‐‐inputdir=test
MODULES





=
$(patsubst
%.c,%,$(wildcard
src/*.c))

PG_CONFIG



=
pg_config
PG91

=
$(shell
$(PG_CONFIG)
‐‐version



|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes)

 Makefile
Update the Makefile
PG91

=
$(shell
$(PG_CONFIG)
‐‐version



|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes)




 Makefile
Update the Makefile
PG91

=
$(shell
$(PG_CONFIG)
‐‐version



|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes)

ifeq
($(PG91),yes)
all:
sql/$(EXTENSION)‐‐$(EXTVERSION).sql

sql/$(EXTENSION)‐‐$(EXTVERSION).sql:
sql/$(EXTENSION).sql

 cp
$<
$@

DATA
=
$(wildcard
sql/*‐‐*.sql)
sql/$(EXTENSION)‐‐$
(EXTVERSION).sql
EXTRA_CLEAN
=
sql/$(EXTENSION)‐‐$(EXTVERSION).sql
endif

PGXS
:=
$(shell
$(PG_CONFIG)
‐‐pgxs)
include
$(PGXS)
 Makefile
Update the Makefile
PG91

=
$(shell
$(PG_CONFIG)
‐‐version



|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes)

ifeq
($(PG91),yes)
all:
sql/$(EXTENSION)‐‐$(EXTVERSION).sql

sql/$(EXTENSION)‐‐$(EXTVERSION).sql:
sql/$(EXTENSION).sql

 cp
$<
$@

DATA
=
$(wildcard
sql/*‐‐*.sql)
sql/$(EXTENSION)‐‐$
(EXTVERSION).sql
EXTRA_CLEAN
=
sql/$(EXTENSION)‐‐$(EXTVERSION).sql
endif

PGXS
:=
$(shell
$(PG_CONFIG)
‐‐pgxs)
include
$(PGXS)
 Makefile
Update the Makefile
PG91

=
$(shell
$(PG_CONFIG)
‐‐version



|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes)

ifeq
($(PG91),yes)
all:
sql/$(EXTENSION)‐‐$(EXTVERSION).sql

sql/$(EXTENSION)‐‐$(EXTVERSION).sql:
sql/$(EXTENSION).sql

 cp
$<
$@

DATA
=
$(wildcard
sql/*‐‐*.sql)
sql/$(EXTENSION)‐‐$
(EXTVERSION).sql
EXTRA_CLEAN
=
sql/$(EXTENSION)‐‐$(EXTVERSION).sql
endif

PGXS
:=
$(shell
$(PG_CONFIG)
‐‐pgxs)
include
$(PGXS)
 Makefile
Update the Makefile
PG91

=
$(shell
$(PG_CONFIG)
‐‐version



|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes)

ifeq
($(PG91),yes)
all:
sql/$(EXTENSION)‐‐$(EXTVERSION).sql

sql/$(EXTENSION)‐‐$(EXTVERSION).sql:
sql/$(EXTENSION).sql

 cp
$<
$@

DATA
=
$(wildcard
sql/*‐‐*.sql)
sql/$(EXTENSION)‐‐$
(EXTVERSION).sql
EXTRA_CLEAN
=
sql/$(EXTENSION)‐‐$(EXTVERSION).sql
endif

PGXS
:=
$(shell
$(PG_CONFIG)
‐‐pgxs)
include
$(PGXS)
 Makefile
Update the Makefile
PG91

=
$(shell
$(PG_CONFIG)
‐‐version



|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes)

ifeq
($(PG91),yes)
all:
sql/$(EXTENSION)‐‐$(EXTVERSION).sql

sql/$(EXTENSION)‐‐$(EXTVERSION).sql:
sql/$(EXTENSION).sql

 cp
$<
$@

DATA
=
$(wildcard
sql/*‐‐*.sql)
sql/$(EXTENSION)‐‐$
(EXTVERSION).sql
EXTRA_CLEAN
=
sql/$(EXTENSION)‐‐$(EXTVERSION).sql
endif

PGXS
:=
$(shell
$(PG_CONFIG)
‐‐pgxs)
include
$(PGXS)
 Makefile
Update the Makefile
PG91

=
$(shell
$(PG_CONFIG)
‐‐version



|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes)

ifeq
($(PG91),yes)
all:
sql/$(EXTENSION)‐‐$(EXTVERSION).sql

sql/$(EXTENSION)‐‐$(EXTVERSION).sql:
sql/$(EXTENSION).sql

 cp
$<
$@

DATA
=
$(wildcard
sql/*‐‐*.sql)
sql/$(EXTENSION)‐‐$
(EXTVERSION).sql
EXTRA_CLEAN
=
sql/$(EXTENSION)‐‐$(EXTVERSION).sql
endif

PGXS
:=
$(shell
$(PG_CONFIG)
‐‐pgxs)
include
$(PGXS)
 Makefile
Update the Makefile
PG91

=
$(shell
$(PG_CONFIG)
‐‐version



|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes)

ifeq
($(PG91),yes)
all:
sql/$(EXTENSION)‐‐$(EXTVERSION).sql

sql/$(EXTENSION)‐‐$(EXTVERSION).sql:
sql/$(EXTENSION).sql

 cp
$<
$@

DATA
=
$(wildcard
sql/*‐‐*.sql)
sql/$(EXTENSION)‐‐$
(EXTVERSION).sql
EXTRA_CLEAN
=
sql/$(EXTENSION)‐‐$(EXTVERSION).sql
endif

PGXS
:=
$(shell
$(PG_CONFIG)
‐‐pgxs)
include
$(PGXS)
 Makefile
Update the Makefile
PG91

=
$(shell
$(PG_CONFIG)
‐‐version



|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes)

ifeq
($(PG91),yes)
all:
sql/$(EXTENSION)‐‐$(EXTVERSION).sql

sql/$(EXTENSION)‐‐$(EXTVERSION).sql:
sql/$(EXTENSION).sql

 cp
$<
$@

DATA
=
$(wildcard
sql/*‐‐*.sql)
sql/$(EXTENSION)‐‐$
(EXTVERSION).sql
EXTRA_CLEAN
=
sql/$(EXTENSION)‐‐$(EXTVERSION).sql
endif

PGXS
:=
$(shell
$(PG_CONFIG)
‐‐pgxs)
include
$(PGXS)
 Makefile
Or Forget It
Or Forget It
Copy Makefile
Or Forget It
Copy Makefile

Edit first line
Or Forget It
Copy Makefile

Edit first line

   EXTENSION=pair
Or Forget It
Copy Makefile

Edit first line

   EXTENSION=pair

Ignore the rest
Or Forget It
Copy Makefile

Edit first line

   EXTENSION=pair

Ignore the rest




             Better still…
Skeleton in the Closet
%
Skeleton in the Closet
%


sudo
gem
install
pgxn_utils
Installing
ri
documentation
for
pgxn_utils‐0.1.2...
Installing
RDoc
documentation
for
pgxn_utils‐0.1.2...
%
Skeleton in the Closet
%


sudo
gem
install
pgxn_utils
Installing
ri
documentation
for
pgxn_utils‐0.1.2...
Installing
RDoc
documentation
for
pgxn_utils‐0.1.2...


pgxn_utils
skeleton
semver
%






create

semver






create

semver/semver.control






create

semver/META.json






create

semver/Makefile






create

semver/README.md






create

semver/doc/semver.md






create

semver/sql/semver.sql






create

semver/sql/uninstall_semver.sql






create

semver/test/expected/base.out






create

semver/test/sql/base.sql
%
Thank you
Dickson S. Guedes
Install Extension
%
Install Extension


pgxn
load
pair
‐d
try
%
INFO:
best
version:
pair
0.1.2
CREATE
EXTENSION
%

    Nice.
Resources
Resources
http:/
     /pgxn.org/ — Browse
Resources
http:/
     /pgxn.org/ — Browse

http:/
     /manager.pgxn.org/ — Release
Resources
http:/
     /pgxn.org/ — Browse

http:/
     /manager.pgxn.org/ — Release

http:/
     /s.coop/pgextdocs — Learn
Resources
http:/
     /pgxn.org/ — Browse

http:/
     /manager.pgxn.org/ — Release

http:/
     /s.coop/pgextdocs — Learn

http:/
     /s.coop/4y4 — Slides
Resources
http:/
     /pgxn.org/ — Browse

http:/
     /manager.pgxn.org/ — Release

http:/
     /s.coop/pgextdocs — Learn

http:/
     /s.coop/4y4 — Slides

http:/
     /s.coop/pgxnclient — Client
Resources
http:/
     /pgxn.org/ — Browse

http:/
     /manager.pgxn.org/ — Release

http:/
     /s.coop/pgextdocs — Learn

http:/
     /s.coop/4y4 — Slides

http:/
     /s.coop/pgxnclient — Client

http:/
     /s.coop/pgxnutils — Develop
Building and Distributing
     PostgreSQL Extensions
      Without Learning C
                                               David E. Wheeler
                                            PostgreSQL Experts, Inc.

                                                 PGXPUG PgDay 2011




Text: Attribution-Noncommercial-Share Alike 3.0 United States:
http://creativecommons.org/licenses/by-nc-sa/3.0/us/
Images licensed independently and © Their respective owners.

More Related Content

PDF
Metadata-driven Testing
PDF
Findbin libs
PDF
Hypers and Gathers and Takes! Oh my!
PDF
The $path to knowledge: What little it take to unit-test Perl.
PDF
BSDM with BASH: Command Interpolation
PDF
Get your teeth into Plack
PDF
Perl6 Regexen: Reduce the line noise in your code.
PDF
PhpSpec 2.0 ilustrated by examples
Metadata-driven Testing
Findbin libs
Hypers and Gathers and Takes! Oh my!
The $path to knowledge: What little it take to unit-test Perl.
BSDM with BASH: Command Interpolation
Get your teeth into Plack
Perl6 Regexen: Reduce the line noise in your code.
PhpSpec 2.0 ilustrated by examples

What's hot (20)

PDF
Forget about Index.php and build you applications around HTTP - PHPers Cracow
PPTX
New in php 7
PDF
Puppet Camp Paris 2015: Power of Puppet 4 (Beginner)
PDF
Symfony without the framework
PDF
Barely Legal Xxx Perl Presentation
PDF
PHP 良好實踐 (Best Practice)
PDF
Perl6 grammars
ODP
Advanced Perl Techniques
PDF
Good Evils In Perl
PDF
Ethiopian multiplication in Perl6
PDF
I, For One, Welcome Our New Perl6 Overlords
PDF
PHP 5.3 Overview
PDF
Best Practices in Plugin Development (WordCamp Seattle)
ODP
Introduction to Perl - Day 2
PDF
PHP traits, treat or threat?
ODP
Symfony CMF - PHP Conference Brazil 2011
KEY
Good Evils In Perl (Yapc Asia)
PDF
Forget about index.php and build you applications around HTTP!
ODP
Introduction to Perl - Day 1
ODP
Introduction to Modern Perl
Forget about Index.php and build you applications around HTTP - PHPers Cracow
New in php 7
Puppet Camp Paris 2015: Power of Puppet 4 (Beginner)
Symfony without the framework
Barely Legal Xxx Perl Presentation
PHP 良好實踐 (Best Practice)
Perl6 grammars
Advanced Perl Techniques
Good Evils In Perl
Ethiopian multiplication in Perl6
I, For One, Welcome Our New Perl6 Overlords
PHP 5.3 Overview
Best Practices in Plugin Development (WordCamp Seattle)
Introduction to Perl - Day 2
PHP traits, treat or threat?
Symfony CMF - PHP Conference Brazil 2011
Good Evils In Perl (Yapc Asia)
Forget about index.php and build you applications around HTTP!
Introduction to Perl - Day 1
Introduction to Modern Perl
Ad

Viewers also liked (6)

KEY
Test Driven Database Development
KEY
Simple SQL Change Management with Sqitch
KEY
Sane SQL Change Management with Sqitch
KEY
PgTAP Best Practices
PPT
Lightning Talk - pgTAP
PPTX
Lovingly crafting a mountain, not by hand: managing piles of metadata
Test Driven Database Development
Simple SQL Change Management with Sqitch
Sane SQL Change Management with Sqitch
PgTAP Best Practices
Lightning Talk - pgTAP
Lovingly crafting a mountain, not by hand: managing piles of metadata
Ad

Similar to Building and Distributing PostgreSQL Extensions Without Learning C (20)

PDF
PostgreSQL - Case Study
ODP
Introduction to PostgreSQL
PDF
Building a Complex, Real-Time Data Management Application
KEY
PostgreSQL
PDF
PostgreSQL, Extensible to the Nth Degree: Functions, Languages, Types, Rules,...
PPTX
PostgreSQL - It's kind've a nifty database
PDF
Plpgsql internals
ODP
PostgreSQL 8.4 TriLUG 2009-11-12
PDF
Heroku Postgres SQL Tips, Tricks, Hacks
PPTX
PostgreSQL - Object Relational Database
PDF
0292-introduction-postgresql.pdf
KEY
Releasing PostgreSQL Extension on PGXN
PDF
An evening with Postgresql
PDF
Letters from the open source trenches - Postgres community
PDF
8.4 Upcoming Features
PDF
The Accidental DBA
PDF
PostgreSQL and PL/Java
PDF
Extensions on PostgreSQL
PPTX
PLPgSqL- Datatypes, Language structure.pptx
PDF
9.1 Grand Tour
PostgreSQL - Case Study
Introduction to PostgreSQL
Building a Complex, Real-Time Data Management Application
PostgreSQL
PostgreSQL, Extensible to the Nth Degree: Functions, Languages, Types, Rules,...
PostgreSQL - It's kind've a nifty database
Plpgsql internals
PostgreSQL 8.4 TriLUG 2009-11-12
Heroku Postgres SQL Tips, Tricks, Hacks
PostgreSQL - Object Relational Database
0292-introduction-postgresql.pdf
Releasing PostgreSQL Extension on PGXN
An evening with Postgresql
Letters from the open source trenches - Postgres community
8.4 Upcoming Features
The Accidental DBA
PostgreSQL and PL/Java
Extensions on PostgreSQL
PLPgSqL- Datatypes, Language structure.pptx
9.1 Grand Tour

Recently uploaded (20)

PDF
Chapter 3 Spatial Domain Image Processing.pdf
PDF
Building Integrated photovoltaic BIPV_UPV.pdf
PDF
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
PDF
How UI/UX Design Impacts User Retention in Mobile Apps.pdf
PDF
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
PDF
KodekX | Application Modernization Development
PPTX
20250228 LYD VKU AI Blended-Learning.pptx
PDF
CIFDAQ's Market Insight: SEC Turns Pro Crypto
PDF
Encapsulation_ Review paper, used for researhc scholars
PDF
Network Security Unit 5.pdf for BCA BBA.
PDF
Reach Out and Touch Someone: Haptics and Empathic Computing
PPTX
PA Analog/Digital System: The Backbone of Modern Surveillance and Communication
PDF
Diabetes mellitus diagnosis method based random forest with bat algorithm
PPTX
Digital-Transformation-Roadmap-for-Companies.pptx
PDF
Spectral efficient network and resource selection model in 5G networks
PDF
Advanced methodologies resolving dimensionality complications for autism neur...
PPTX
Big Data Technologies - Introduction.pptx
PDF
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
PDF
Empathic Computing: Creating Shared Understanding
PDF
Per capita expenditure prediction using model stacking based on satellite ima...
Chapter 3 Spatial Domain Image Processing.pdf
Building Integrated photovoltaic BIPV_UPV.pdf
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
How UI/UX Design Impacts User Retention in Mobile Apps.pdf
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
KodekX | Application Modernization Development
20250228 LYD VKU AI Blended-Learning.pptx
CIFDAQ's Market Insight: SEC Turns Pro Crypto
Encapsulation_ Review paper, used for researhc scholars
Network Security Unit 5.pdf for BCA BBA.
Reach Out and Touch Someone: Haptics and Empathic Computing
PA Analog/Digital System: The Backbone of Modern Surveillance and Communication
Diabetes mellitus diagnosis method based random forest with bat algorithm
Digital-Transformation-Roadmap-for-Companies.pptx
Spectral efficient network and resource selection model in 5G networks
Advanced methodologies resolving dimensionality complications for autism neur...
Big Data Technologies - Introduction.pptx
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
Empathic Computing: Creating Shared Understanding
Per capita expenditure prediction using model stacking based on satellite ima...

Building and Distributing PostgreSQL Extensions Without Learning C

Editor's Notes