Skip to content

Commit

Permalink
The extensions to be loaded/unloaded can be specified in the command …
Browse files Browse the repository at this point in the history
…line
  • Loading branch information
dvarrazzo committed Nov 26, 2011
1 parent ad620fa commit 037eab4
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 42 deletions.
20 changes: 11 additions & 9 deletions docs/usage.rst
Expand Up @@ -200,7 +200,7 @@ Usage:
pgxn load [--help] [--stable | --testing | --unstable] [-d *DBNAME*]
[-h *HOST*] [-p *PORT*] [-U *NAME*] [--pg_config *PATH*]
[--schema *SCHEMA*]
*SPEC*
*SPEC* [*EXT* [*EXT* ...]]
The distribution is specified according to the `package specification`_ and
can refer to a local directory or file. No consistency check is performed
Expand Down Expand Up @@ -240,7 +240,9 @@ confirmation.

If the distribution provides more than one extension, the extensions are
loaded in the order in which they are specified in the ``provides`` section of
the ``META.json`` file.
the ``META.json`` file. It is also possilbe to load only a few of the
extensions provided, specifying them after *SPEC*: the extensions will be
loaded in the order specified.

If a *SCHEMA* is specified, the extensions are loaded in the provided schema.
Note that if ``CREATE EXTENSION`` is used, the schema is directly supported;
Expand All @@ -249,8 +251,6 @@ the provided schema (a confirmation will be asked before attempting loading).

.. _'provides' section: http://pgxn.org/spec/#provides

.. todo:: Add options to specify what to load


.. _unload:

Expand All @@ -267,11 +267,11 @@ Usage:
pgxn unload [--help] [--stable | --testing | --unstable] [-d *DBNAME*]
[-h *HOST*] [-p *PORT*] [-U *NAME*] [--pg_config *PATH*]
[--schema *SCHEMA*]
*SPEC*
*SPEC* [*EXT* [*EXT* ...]]
The command does the opposite of the load_ command: it drops an extension from
the specified database, either issuing a `DROP EXTENSION`_ command or running
an uninstall script eventually provided.
The command does the opposite of the load_ command: it drops a distribution
extensions from the specified database, either issuing `DROP EXTENSION`_
commands or running uninstall scripts eventually provided.

For every extension specified in the `'provides' section`_ of the
distribution ``META.json``, the command will look for a file called
Expand All @@ -288,7 +288,9 @@ itself, so the option will be ignored.

If the distribution specifies more than one extension, they are unloaded in
reverse order respect to the order in which they are specified in the
``META.json`` file.
``META.json`` file. It is also possilbe to unload only a few of the
extensions provided, specifying them after *SPEC*: the extensions will be
unloaded in the order specified.

.. _DROP EXTENSION: http://www.postgresql.org/docs/9.1/static/sql-dropextension.html

Expand Down
83 changes: 55 additions & 28 deletions pgxnclient/commands/install.py
Expand Up @@ -189,6 +189,9 @@ def customize_parser(self, parser, subparsers, **kwargs):
type=Identifier.parse_arg,
help=_("use SCHEMA instead of the default schema"))

subp.add_argument('extensions', metavar='EXT', nargs='*',
help = _("only specified extensions [default: all]"))

return subp

def get_pg_version(self):
Expand Down Expand Up @@ -332,28 +335,60 @@ def _check_schema_exists(self, schema):
raise PgxnClientException(
"schema %s does not exist" % schema)

def _get_extensions(self):
"""
Return a list of pairs (name, sql file) to be loaded/unloaded.
class Load(LoadUnload):
name = 'load'
description = N_("load a distribution's extensions into a database")

def run(self):
Items are in loading order.
"""
spec = self.get_spec()
dist = self.get_meta(spec)

if 'provides' in dist:
if 'provides' not in dist:
# No 'provides' specified: assume a single extension named
# after the distribution. This is automatically done by PGXN,
# but we should do ourselves to deal with local META files
# not mangled by the PGXN upload script yet.
name = dist['name']
for ext in self.opts.extensions:
if ext <> name:
raise PgxnClientException(
"can't find extension '%s' in the distribution '%s'"
% (name, spec))

return [ (name, None) ]

rv = []

if not self.opts.extensions:
# All the extensions, in the order specified
if len(dist['provides']) > 1 and sys.version_info < (2, 5):
logger.warn(_("can't guarantee extensions load order "
"with Python < 2.5"))
for name, data in dist['provides'].items():
sql = data.get('file')
self.load_ext(name, sql)
rv.append((name, data.get('file')))
else:
# No 'provides' specified: assume a single extension named
# after the distribution. This is automatically done by PGXN,
# but we should do ourselves to deal with local META files
# not mangled by the PGXN upload script yet.
self.load_ext(dist['name'], None)
# Only the specified extensions
for name in self.opts.extensions:
try:
data = dist['provides'][name]
except KeyError:
raise PgxnClientException(
"can't find extension '%s' in the distribution '%s'"
% (name, spec))
rv.append((name, data.get('file')))

return rv


class Load(LoadUnload):
name = 'load'
description = N_("load a distribution's extensions into a database")

def run(self):
items = self._get_extensions()
for (name, sql) in items:
self.load_ext(name, sql)

def load_ext(self, name, sqlfile):
logger.debug(_("loading extension '%s' with file: %s"),
Expand Down Expand Up @@ -417,23 +452,15 @@ class Unload(LoadUnload):
description = N_("unload a distribution's extensions from a database")

def run(self):
spec = self.get_spec()
dist = self.get_meta(spec)
items = self._get_extensions()

if 'provides' in dist:
if len(dist['provides']) > 1 and sys.version_info < (2, 5):
logger.warn(_("can't guarantee extensions load order "
"with Python < 2.5"))
provs = dist['provides'].items()
provs.reverse()
for name, data in provs:
sql = data.get('file')
self.load_ext(name, sql)
else:
# Assume a single extension in the distribution - see Install
self.load_ext(dist['name'], None)
if not self.opts.extensions:
items.reverse()

def load_ext(self, name, sqlfile):
for (name, sql) in items:
self.unload_ext(name, sql)

def unload_ext(self, name, sqlfile):
logger.debug(_("unloading extension '%s' with file: %s"),
name, sqlfile)

Expand Down
118 changes: 113 additions & 5 deletions pgxnclient/tests/test_commands.py
Expand Up @@ -6,7 +6,7 @@
from urllib import quote

from pgxnclient.utils import b
from pgxnclient.errors import ResourceNotFound
from pgxnclient.errors import PgxnClientException, ResourceNotFound
from pgxnclient.tests import unittest
from pgxnclient.tests.testutils import ifunlink, get_test_filename

Expand Down Expand Up @@ -281,8 +281,6 @@ def test_install_fails(self, mock_get, mock_popen):
pop.returncode = 1

from pgxnclient.cli import main
from pgxnclient.errors import PgxnClientException

self.assertRaises(PgxnClientException, main, ['install', 'foobar'])

self.assertEquals(mock_popen.call_count, 1)
Expand Down Expand Up @@ -405,7 +403,6 @@ def test_check_fails(self, mock_get, mock_popen):
pop.returncode = 1

from pgxnclient.cli import main
from pgxnclient.errors import PgxnClientException

self.assertRaises(PgxnClientException, main, ['check', 'foobar'])

Expand All @@ -432,7 +429,6 @@ def create_regression_files(*args, **kwargs):
"Please remove temp file 'regression.diffs' from current dir")

from pgxnclient.cli import main
from pgxnclient.errors import PgxnClientException

try:
self.assertRaises(PgxnClientException, main, ['check', 'foobar'])
Expand Down Expand Up @@ -624,6 +620,118 @@ def test_unload_extensions_order(self, mock_popen, mock_pgver, mock_isext):
self.assertEquals(pop.communicate.call_args_list[3][0][0],
'DROP EXTENSION foo;')

@patch('pgxnclient.commands.install.Load.is_extension')
@patch('pgxnclient.commands.install.Load.get_pg_version')
@patch('pgxnclient.commands.Popen')
def test_load_list(self, mock_popen, mock_pgver, mock_isext):
pop = mock_popen.return_value
pop.returncode = 0
mock_pgver.return_value = (9,1,0)
mock_isext.return_value = True

tdir = tempfile.mkdtemp()
try:
from pgxnclient.utils.zip import unpack
dir = unpack(get_test_filename('foobar-0.42.1.zip'), tdir)
shutil.copyfile(
get_test_filename('META-manyext.json'),
os.path.join(dir, 'META.json'))

from pgxnclient.cli import main
main(['load', '--yes', dir, 'baz', 'foo'])

finally:
shutil.rmtree(tdir)

self.assertEquals(mock_popen.call_count, 2)
self.assert_('psql' in mock_popen.call_args[0][0][0])
self.assertEquals(pop.communicate.call_args_list[0][0][0],
'CREATE EXTENSION baz;')
self.assertEquals(pop.communicate.call_args_list[1][0][0],
'CREATE EXTENSION foo;')

@patch('pgxnclient.commands.install.Unload.is_extension')
@patch('pgxnclient.commands.install.Unload.get_pg_version')
@patch('pgxnclient.commands.Popen')
def test_unload_list(self, mock_popen, mock_pgver, mock_isext):
pop = mock_popen.return_value
pop.returncode = 0
mock_pgver.return_value = (9,1,0)
mock_isext.return_value = True

tdir = tempfile.mkdtemp()
try:
from pgxnclient.utils.zip import unpack
dir = unpack(get_test_filename('foobar-0.42.1.zip'), tdir)
shutil.copyfile(
get_test_filename('META-manyext.json'),
os.path.join(dir, 'META.json'))

from pgxnclient.cli import main
main(['unload', '--yes', dir, 'baz', 'foo'])

finally:
shutil.rmtree(tdir)

self.assertEquals(mock_popen.call_count, 2)
self.assert_('psql' in mock_popen.call_args[0][0][0])
self.assertEquals(pop.communicate.call_args_list[0][0][0],
'DROP EXTENSION baz;')
self.assertEquals(pop.communicate.call_args_list[1][0][0],
'DROP EXTENSION foo;')

@patch('pgxnclient.commands.install.Load.is_extension')
@patch('pgxnclient.commands.install.Load.get_pg_version')
@patch('pgxnclient.commands.Popen')
def test_load_missing(self, mock_popen, mock_pgver, mock_isext):
pop = mock_popen.return_value
pop.returncode = 0
mock_pgver.return_value = (9,1,0)
mock_isext.return_value = True

tdir = tempfile.mkdtemp()
try:
from pgxnclient.utils.zip import unpack
dir = unpack(get_test_filename('foobar-0.42.1.zip'), tdir)
shutil.copyfile(
get_test_filename('META-manyext.json'),
os.path.join(dir, 'META.json'))

from pgxnclient.cli import main
self.assertRaises(PgxnClientException, main,
['load', '--yes', dir, 'foo', 'ach'])

finally:
shutil.rmtree(tdir)

self.assertEquals(mock_popen.call_count, 0)

@patch('pgxnclient.commands.install.Unload.is_extension')
@patch('pgxnclient.commands.install.Unload.get_pg_version')
@patch('pgxnclient.commands.Popen')
def test_unload_missing(self, mock_popen, mock_pgver, mock_isext):
pop = mock_popen.return_value
pop.returncode = 0
mock_pgver.return_value = (9,1,0)
mock_isext.return_value = True

tdir = tempfile.mkdtemp()
try:
from pgxnclient.utils.zip import unpack
dir = unpack(get_test_filename('foobar-0.42.1.zip'), tdir)
shutil.copyfile(
get_test_filename('META-manyext.json'),
os.path.join(dir, 'META.json'))

from pgxnclient.cli import main
self.assertRaises(PgxnClientException, main,
['unload', '--yes', dir, 'foo', 'ach'])

finally:
shutil.rmtree(tdir)

self.assertEquals(mock_popen.call_count, 0)


class SearchTestCase(unittest.TestCase):
@patch('sys.stdout')
Expand Down

0 comments on commit 037eab4

Please sign in to comment.