Repository: dbt-labs/dbt-core

Issue:
operations
Add new CLI task, `operation`, which runs one-off sql commands. These operations could include table maintenance tasks or other one-off queries.

```
$ dbt run operation compress_table --schema snowplow --table events
$ dbt run operation vacuum_analyze --schema snowplow
```

Operations should be built just like macros. Some arguments should be required while others are optional.


Discussion:

Resolving patch:
diff --git a/core/dbt/adapters/base/impl.py b/core/dbt/adapters/base/impl.py
--- a/core/dbt/adapters/base/impl.py
+++ b/core/dbt/adapters/base/impl.py
@@ -744,9 +744,16 @@ def execute_macro(self, macro_name, manifest=None, project=None,
 
         macro = manifest.find_macro_by_name(macro_name, project)
         if macro is None:
+            if project is None:
+                package_name = 'any package'
+            else:
+                package_name = 'the "{}" package'.format(project)
+
+            # The import of dbt.context.runtime below shadows 'dbt'
+            import dbt.exceptions
             raise dbt.exceptions.RuntimeException(
-                'Could not find macro with name {} in project {}'
-                .format(macro_name, project)
+                'dbt could not find a macro with the name "{}" in {}'
+                .format(macro_name, package_name)
             )
 
         # This causes a reference cycle, as dbt.context.runtime.generate()
diff --git a/core/dbt/main.py b/core/dbt/main.py
--- a/core/dbt/main.py
+++ b/core/dbt/main.py
@@ -20,6 +20,7 @@
 import dbt.task.generate as generate_task
 import dbt.task.serve as serve_task
 import dbt.task.freshness as freshness_task
+import dbt.task.run_operation as run_operation_task
 from dbt.adapters.factory import reset_adapters
 
 import dbt.tracking
@@ -723,6 +724,33 @@ def parse_args(args):
     _build_test_subparser(subs, base_subparser)
     _build_source_snapshot_freshness_subparser(source_subs, base_subparser)
 
+    sub = subs.add_parser(
+        'run-operation',
+        parents=[base_subparser],
+        help="""
+            (beta) Run the named macro with any supplied arguments. This
+            subcommand is unstable and subject to change in a future release
+            of dbt. Please use it with caution"""
+    )
+    sub.add_argument(
+        '--macro',
+        required=True,
+        help="""
+            Specify the macro to invoke. dbt will call this macro with the
+            supplied arguments and then exit"""
+    )
+    sub.add_argument(
+        '--args',
+        type=str,
+        default='{}',
+        help="""
+            Supply arguments to the macro. This dictionary will be mapped
+            to the keyword arguments defined in the selected macro. This
+            argument should be a YAML string, eg. '{my_variable: my_value}'"""
+    )
+    sub.set_defaults(cls=run_operation_task.RunOperationTask,
+                     which='run-operation')
+
     if len(args) == 0:
         p.print_help()
         sys.exit(1)
diff --git a/core/dbt/task/run_operation.py b/core/dbt/task/run_operation.py
new file mode 100644
--- /dev/null
+++ b/core/dbt/task/run_operation.py
@@ -0,0 +1,40 @@
+from dbt.logger import GLOBAL_LOGGER as logger
+
+from dbt.task.base_task import BaseTask
+from dbt.adapters.factory import get_adapter
+from dbt.loader import GraphLoader
+
+import dbt
+import dbt.utils
+import dbt.exceptions
+
+
+class RunOperationTask(BaseTask):
+    def _get_macro_parts(self):
+        macro_name = self.args.macro
+        if '.' in macro_name:
+            package_name, macro_name = macro_name.split(".", 1)
+        else:
+            package_name = None
+
+        return package_name, macro_name
+
+    def _get_kwargs(self):
+        return dbt.utils.parse_cli_vars(self.args.args)
+
+    def run(self):
+        manifest = GraphLoader.load_all(self.config)
+        adapter = get_adapter(self.config)
+
+        package_name, macro_name = self._get_macro_parts()
+        macro_kwargs = self._get_kwargs()
+
+        res = adapter.execute_macro(
+            macro_name,
+            project=package_name,
+            kwargs=macro_kwargs,
+            manifest=manifest,
+            connection_name="macro_{}".format(macro_name)
+        )
+
+        return res


Test patch: