Repository: honeycombio/terraform-provider-honeycombio (language: python)

Issue:
RFC: Provider Breaking Changes
# Overview

Honeycomb has recently taken ownership of the Terraform Provider which was originally developed and maintained by community member Koenraad Verheyden (thank you! 🙏🏻 ).

Leading up to the first Honeycomb-owned release of the Provider there are three breaking changes we feel need to be made:

1. The `honeycombio_query` data source will be renamed to `honeycombio_query_specification`. Initially raised in #65.
1. The `honeycombio_board` resource will no longer support inline Query JSON and instead require that you create a `honeycombio_query` resource and pass the ID in the list of queries on the Board. Initially raised in #55, and currently blocking #84 and #82.
1. The `honeycombio_trigger` resource will no longer support inline Query JSON and instead require that you create a `honeycombio_query` resource and pass the ID to the Trigger resource. Also raised in #55.

As the move to the Honeycomb-maintained version of the provider will require that you update the namespace used in your `required_providers` configuration from `kvrhdn` to `honeycombio` we hope that these changes will be more easily managed.

This informal RFC was written to offer transparency around these changes and give the provider's user community a chance to comment on the changes.

## honeycombio_query data source change

This datasource never allowed you to fetch an existing Query object, but instead helped you form a valid Query Specification in HCL. Renaming this to `honeycombio_query_specification` seems most accurate and the change should easily be handled by a simple ‘find and replace’.

## honeycombio_board resource change

The Provider currently supports providing one or more inline queries (as Query Spec JSON `query_json`) to a Board resource to create a Board. This was created before either of the Query API or Query Annotations API existed, but both customers and the field team are looking to be able to build Boards via Terraform made up of fully annotated queries managed in code.

Inline queries are problematic because providing the query JSON to the API makes a Query object for you which is then difficult to track in Terraform as it’s a “computed sometimes” attribute of the resource. The explicit creation of a Query resource also feels more declarative and ✨ Terraform-ish ✨ .

Boards with Terraform today look something like this:

```
resource “honeycombio_board” “myboard” {
    name  = "Test board managed by Terraform"
    style = "list"

    query {
      caption       = "test query"
      dataset       = var.dataset
      query_json    = data.honeycombio_query.test.json
    }
    query {
      caption        = "my verbose query"
      query_style    = "combo"
      dataset        = var.dataset
      // Inline query
      query_json  = <<EOH
{
    "time_range": 604800,
<truncated for brevity>
}
EOH
    }
}
```

The current Board resource implementation ignores the returned `query_id` completely.

### Proposed change

The change to the Board resource’s schema will remove the ability to provide a Query Specification directly and instead require that you make use of the existing Query resource to construct Queries – with the recommendation that you do so with the assistance of the (newly renamed) Query Specification data source.

The above would turn into something like this:

```
resource “honeycombio_query” “helpful-query” {
   dataset       = var.dataset
   query_json    = data.honeycombio_query_specification.helpful.json
}

resource “honeycombio_query” “other-query” {
   dataset     = var.dataset
   query_json  = data.honeycombio_query_specification.other.json
}

resource “honeycombio_board” “myboard” {
    name  = "Test board managed by Terraform"
    style   = "list"

    query {
      caption       = "A very Helpful Query"
      dataset       = var.dataset
      query_id      = honeycombio_query.helpful-query.id
    }
    query {
      caption        = "my verbose query"
      query_style    = "table"
      dataset        = var.dataset
      query_id       = honeycombio_query.other-query.id
    }
}
```

The above paves the way for cleanly making use of the Query Annotation resource to name and describe the queries on the Board.

### Abandoned Ideas

- Adding an “inline_query” type to the Board resource which allowed you to provide the Query Spec as JSON and would track the returned “query_id” as a “Calculated” schema attribute. Abandoned as it was still a breaking change (all current “query” would need to be moved to “inline_query”)
- Attempting to use the Terraform Plugin SDK’s experimental “GetRawConfig” to extract the parsed HCL and determine if “query_json” was in use. This was ugly and felt like the sort of brittle that would break in a future version of the Provider SDK.

## honeycombio_trigger resource change

Similar to the Board resource, the Provider currently supports passing an inline Query (as Query Specification JSON) to create a Trigger. The explicit creation of a Query resource also feels more declarative and ✨ Terraform-ish ✨ .

This change is ultimately intended to bring more consistency to the Provider’s interface.

Triggers in Terraform today look something like this:

```
resource “honeycombio_trigger” “mytrigger” {
  name       = "Trigger managed by Terraform"
  dataset    = var.dataset
  query_json = data.honeycombio_query.mytrigger.json

  frequency = 300

  threshold {
    op    = ">"
    value = 50
  }
  
  recipient {
    type   = "email"
    target = "hello@example.com"
  }
}
```

### Proposed change

The change to the Trigger resource’s schema will remove the ability to provide a Query Specification directly and instead require that you make use of the existing Query resource to construct Queries – with the recommendation that you do so with the assistance of the (newly renamed) Query Specification data source.

The above would turn into something like this:

```
resource “honeycombio_query” “mytrigger” {
  dataset    = var.dataset
  query_json = data.honeycombio_query_specification.mytrigger.json
}

resource “honeycombio_trigger” “mytrigger” {
  name     = "Trigger managed by Terraform"
  dataset  = var.dataset
  query_id = honeycombio_query.mytrigger.id

  frequency = 300

  threshold {
    op    = ">"
    value = 50
  }
  
  recipient {
    type   = "email"
    target = "hello@example.com"
  }
}
```


Discussion / hints:
This makes a lot of sense to me. My only concern is around how the provider will handle management of the existing state with these resource changes/additions. I believe that making these changes in the Terraform code followed by an apply would simply show:
- newly created `honeycombio_query_specification` resources
- potential changes to `honeycombio_board` (particularly if query titles are added)
- no changes to `honeycombio_trigger` resources since the query definitions themselves will remain the same

In other words, I _think_ the transition will be seamless. Does that track with your expectations @jharley? 
I'd rather avoid having to do manual imports of resources.
@slizco Correct! These are just breaking changes to the schema which should not cause entire resources to be removed from state or put you into a position of needing to do 'state surgery' (manual imports or `state rm`)
@slizco Wait, I just thought about it a bit more: `honeycombio_trigger` resources will show an update as the Query ID will be changed but it should be an update in place.

Fix patch:
diff --git a/client/trigger.go b/client/trigger.go
index c5811ca0..7f227848 100644
--- a/client/trigger.go
+++ b/client/trigger.go
@@ -2,6 +2,7 @@ package client
 
 import (
 	"context"
+	"encoding/json"
 	"errors"
 	"fmt"
 )
@@ -55,7 +56,8 @@ type Trigger struct {
 	// properties described with and validated by MatchesTriggerSubset.
 	// Additionally, time_range of the query can be at most 1 day and may not
 	// be greater than 4 times the frequency.
-	Query *QuerySpec `json:"query"`
+	Query   *QuerySpec `json:"query,omitempty"`
+	QueryID string     `json:"query_id,omitempty"`
 	// Threshold. This fild is required.
 	Threshold *TriggerThreshold `json:"threshold"`
 	// Frequency describes how often the trigger should run. Frequency is an
@@ -144,6 +146,30 @@ func TriggerRecipientTypes() []TriggerRecipientType {
 	}
 }
 
+func (t *Trigger) MarshalJSON() ([]byte, error) {
+	// aliased type to avoid stack overflows due to recursion
+	type ATrigger Trigger
+
+	if t.QueryID != "" && t.Query != nil {
+		// we can't sent both to the API, so favour QueryID
+		// this doesn't work in the general case, but this
+		// client is now purpose-built for the Terraform provider
+		a := &ATrigger{
+			ID:          t.ID,
+			Name:        t.Name,
+			Description: t.Description,
+			Disabled:    t.Disabled,
+			QueryID:     t.QueryID,
+			Threshold:   t.Threshold,
+			Frequency:   t.Frequency,
+			Recipients:  t.Recipients,
+		}
+		return json.Marshal(&struct{ *ATrigger }{ATrigger: (*ATrigger)(a)})
+	}
+
+	return json.Marshal(&struct{ *ATrigger }{ATrigger: (*ATrigger)(t)})
+}
+
 func (s *triggers) List(ctx context.Context, dataset string) ([]Trigger, error) {
 	var t []Trigger
 	err := s.client.performRequest(ctx, "GET", "/1/triggers/"+urlEncodeDataset(dataset), nil, &t)
diff --git a/docs/data-sources/query.md b/docs/data-sources/query_specification.md
similarity index 97%
rename from docs/data-sources/query.md
rename to docs/data-sources/query_specification.md
index 6df7eea2..14357bd3 100644
--- a/docs/data-sources/query.md
+++ b/docs/data-sources/query_specification.md
@@ -1,4 +1,4 @@
-# Data Source: honeycombio_query
+# Data Source: honeycombio_query_specification
 
 Construct a query that can be used in triggers and boards. For more information about the query specification, check out [Query Specification](https://docs.honeycomb.io/api/query-specification/).
 
@@ -7,7 +7,7 @@ The `json` attribute contains a serialized JSON representation which can be pass
 ## Example Usage
 
 ```hcl
-data "honeycombio_query" "example" {
+data "honeycombio_query_specification" "example" {
   # zero or more calculation blocks
   calculation {
     op     = "AVG"
@@ -34,7 +34,7 @@ data "honeycombio_query" "example" {
 }
 
 output "json_query" {
-    value = data.honeycombio_query.example.json
+    value = data.honeycombio_query_specification.example.json
 }
 ```
 
diff --git a/docs/data-sources/trigger_recipient.md b/docs/data-sources/trigger_recipient.md
index c1d85af8..25e490de 100644
--- a/docs/data-sources/trigger_recipient.md
+++ b/docs/data-sources/trigger_recipient.md
@@ -16,7 +16,7 @@ data "honeycombio_trigger_recipient" "slack" {
   target  = "honeycomb-triggers"
 }
 
-data "honeycombio_query" "example" {
+data "honeycombio_query_specification" "example" {
   calculation {
     op     = "AVG"
     column = "duration_ms"
@@ -26,7 +26,7 @@ data "honeycombio_query" "example" {
 resource "honeycombio_trigger" "example" {
   name        = "Requests are slower than usuals"
 
-  query_json = data.honeycombio_query.example.json
+  query_json = data.honeycombio_query_specification.example.json
   dataset    = var.dataset
 
   threshold {
diff --git a/docs/resources/board.md b/docs/resources/board.md
index cd18f51a..68efcc1d 100644
--- a/docs/resources/board.md
+++ b/docs/resources/board.md
@@ -13,11 +13,11 @@ locals {
   percentiles = ["P50", "P75", "P90", "P95"]
 }
 
-data "honeycombio_query" "query" {
-  count = length(local.percentiles)
+data "honeycombio_query_specification" "query" {
+  for_each = toset(local.percentiles)
 
   calculation {
-    op     = local.percentiles[count.index]
+    op     = local.percentiles[each.key]
     column = "duration_ms"
   }
 
@@ -33,6 +33,13 @@ data "honeycombio_query" "query" {
   }
 }
 
+resource "honeycombio_query" "query" {
+  for_each = toset(local.percentiles)
+
+  dataset    = var.dataset
+  query_json = data.honeycombio_query_specification.query[each.key].json
+}
+
 resource "honeycombio_board" "board" {
   name        = "Request percentiles"
   description = "${join(", ", local.percentiles)} of all requests for ThatSpecialTenant for the last 15 minutes."
@@ -46,7 +53,7 @@ resource "honeycombio_board" "board" {
       caption     = query.value
       query_style = "combo"
       dataset     = var.dataset
-      query_json  = data.honeycombio_query.query[query.key].json
+      query_json  = data.honeycombio_query_specification.query[query.key].json
     }
   }
 }
@@ -63,7 +70,8 @@ The following arguments are supported:
 
 Each board configuration may have zero or more `query` blocks, which accepts the following arguments:
 
-* `query_json` - (Required) A JSON object describing the query according to the [Query Specification](https://docs.honeycomb.io/api/query-specification/#fields-on-a-query-specification). While the JSON can be constructed manually, it is easiest to use the [`honeycombio_query`](../data-sources/query.md) data source.
+* `query_id` - (Required) The ID of the Query to run.
+* `query_annotation_id` - (Optional) The ID of the Query Annotation to associate with this query.
 * `dataset` - (Required) The dataset this query is associated with.
 * `caption` - (Optional) A description of the query that will be displayed on the board. Supports markdown.
 * `query_style` - (Optional) How the query should be displayed within the board, either `graph` (the default), `table` or `combo`.
diff --git a/docs/resources/query.md b/docs/resources/query.md
index e7117730..a8409246 100644
--- a/docs/resources/query.md
+++ b/docs/resources/query.md
@@ -11,7 +11,7 @@ variable "dataset" {
   type = string
 }
 
-data "honeycombio_query" "test_query" {
+data "honeycombio_query_specification" "test_query" {
   calculation {
     op     = "AVG"
     column = "duration_ms"
@@ -26,7 +26,7 @@ data "honeycombio_query" "test_query" {
 
 resource "honeycombio_query" "test_query" {
   dataset = "%s"
-  query_json = data.honeycombio_query.test_query.json
+  query_json = data.honeycombio_query_specification.test_query.json
 }
 ```
 
@@ -35,7 +35,7 @@ resource "honeycombio_query" "test_query" {
 The following arguments are supported:
 
 * `dataset` - (Required) The dataset this query is added to.
-* `query_json` - (Required) A JSON object describing the query according to the [Query Specification](https://docs.honeycomb.io/api/query-specification/#fields-on-a-query-specification). While the JSON can be constructed manually, it is easiest to use the [`honeycombio_query`](../data-sources/query.md) data source.
+* `query_json` - (Required) A JSON object describing the query according to the [Query Specification](https://docs.honeycomb.io/api/query-specification/#fields-on-a-query-specification). While the JSON can be constructed manually, it is easiest to use the [`honeycombio_query_specification`](../data-sources/query_specification.md) data source.
 
 ## Attribute Reference
 
diff --git a/docs/resources/query_annotation.md b/docs/resources/query_annotation.md
index b7118b91..33a5c751 100644
--- a/docs/resources/query_annotation.md
+++ b/docs/resources/query_annotation.md
@@ -3,7 +3,7 @@
 Creates a query annotation in a dataset.
 
 -> **Note** A query annotation points to a specific query. Any change to the query will result in a new query ID and the annotation will no longer apply.
-If you use the "honeycombio_query" to determine the `query_id` parameter (as in the example below), Terraform will destroy the old query annotation and create a new one.
+If you use the "honeycombio_query_specification" to determine the `query_id` parameter (as in the example below), Terraform will destroy the old query annotation and create a new one.
 If this is wrong for your use case, please open an issue in [kvrhdn/terraform-provider-honeycombio](https://github.com/kvrhdn/terraform-provider-honeycombio).
 
 ## Example Usage
@@ -13,7 +13,7 @@ variable "dataset" {
   type = string
 }
 
-data "honeycombio_query" "test_query" {
+data "honeycombio_query_specification" "test_query" {
   calculation {
     op     = "AVG"
     column = "duration_ms"
@@ -28,7 +28,7 @@ data "honeycombio_query" "test_query" {
 
 resource "honeycombio_query" "test_query" {
   dataset    = var.dataset
-  query_json = data.honeycombio_query.test_query.json
+  query_json = data.honeycombio_query_specification.test_query.json
 }
 
 resource "honeycombio_query_annotation" "test_annotation" {
diff --git a/docs/resources/trigger.md b/docs/resources/trigger.md
index fcdd5445..8610bb5b 100644
--- a/docs/resources/trigger.md
+++ b/docs/resources/trigger.md
@@ -9,7 +9,7 @@ variable "dataset" {
   type = string
 }
 
-data "honeycombio_query" "example" {
+data "honeycombio_query_specification" "example" {
   calculation {
     op     = "AVG"
     column = "duration_ms"
@@ -21,11 +21,16 @@ data "honeycombio_query" "example" {
   }
 }
 
+resource "honeycombio_query" "example" {
+  dataset    = var.dataset
+  query_json = data.honeycombio_query_specification.example.json
+}
+
 resource "honeycombio_trigger" "example" {
   name        = "Requests are slower than usuals"
   description = "Average duration of all requests for the last 10 minutes."
 
-  query_json = data.honeycombio_query.example.json
+  query_json = honeycombio_query.example.id
   dataset    = var.dataset
 
   frequency = 600 // in seconds, 10 minutes
@@ -54,14 +59,14 @@ The following arguments are supported:
 
 * `name` - (Required) Name of the trigger.
 * `dataset` - (Required) The dataset this trigger is associated with.
-* `query_json` - (Required) A JSON object describng the query according to the [Query Specification](https://docs.honeycomb.io/api/query-specification/#fields-on-a-query-specification). While the JSON can be constructed manually, it is easiest to use the [`honeycombio_query`](../data-sources/query.md) data source.
+* `query_id` - (Required) The ID of the Query that the Trigger will execute.
 * `threshold` - (Required) A configuration block (described below) describing the threshold of the trigger.
 * `description` - (Optional) Description of the trigger.
 * `disabled` - (Optional) The state of the trigger. If true, the trigger will not be run. Defaults to false.
 * `frequency` - (Optional) The interval (in seconds) in which to check the results of the query’s calculation against the threshold. Value must be divisible by 60 and between 60 and 86400 (between 1 minute and 1 day). Defaults to 900 (15 minutes).
 * `recipient` - (Optional) Zero or more configuration blocks (described below) with the recipients to notify when the trigger fires.
 
--> **NOTE** The query used in a trigger must follow a strict subset: a query must contain exactly one calcuation and may only contain `calculation`, `filter`, `flter_combination` and `breakdowns` fields. This will be validated during the plan phase.
+-> **NOTE** The query used in a Trigger must follow a strict subset: a query must contain exactly one calcuation and may only contain `calculation`, `filter`, `filter_combination` and `breakdowns` fields. The query's duration cannot be more than four times the trigger's frequency.
 
 Each trigger configuration must contain exactly one `threshold` block, which accepts the following arguments:
 
diff --git a/example/board/main.tf b/example/board/main.tf
index 2dc51024..4ac266fe 100644
--- a/example/board/main.tf
+++ b/example/board/main.tf
@@ -14,8 +14,8 @@ locals {
   percentiles = ["P50", "P75", "P90", "P95"]
 }
 
-data "honeycombio_query" "query" {
-  count = length(local.percentiles)
+data "honeycombio_query_specification" "query" {
+  for_each = toset(local.percentiles)
 
   calculation {
     op     = local.percentiles[count.index]
@@ -33,6 +33,13 @@ data "honeycombio_query" "query" {
   }
 }
 
+resource "honeycombio_query" "query" {
+  for_each = to_set(local.percentiles)
+
+  dataset    = var.dataset
+  query_json = data.honeycombio_query_specification.query[each.key].json
+}
+
 resource "honeycombio_board" "board" {
   name        = "Request percentiles"
   description = "${join(", ", local.percentiles)} of all requests for ThatSpecialTenant for the last 15 minutes."
@@ -43,9 +50,9 @@ resource "honeycombio_board" "board" {
     for_each = local.percentiles
 
     content {
-      caption    = query.value
-      dataset    = var.dataset
-      query_json = data.honeycombio_query.query[query.key].json
+      caption = query.value
+      dataset = var.dataset
+      query_id = honeycombio_query.query[query.key].id
     }
   }
 }
diff --git a/example/trigger/main.tf b/example/trigger/main.tf
index 453e5fea..cd634e88 100644
--- a/example/trigger/main.tf
+++ b/example/trigger/main.tf
@@ -10,7 +10,7 @@ variable "dataset" {
   type = string
 }
 
-data "honeycombio_query" "query" {
+data "honeycombio_query_specification" "query" {
   calculation {
     op     = "AVG"
     column = "duration_ms"
@@ -29,14 +29,19 @@ data "honeycombio_query" "query" {
   time_range = 900 // in seconds, 15 minutes
 }
 
+resource "honeycombio_query" "trigger-query" {
+  dataset    = var.dataset
+  query_json = data.honeycombio_query_specification.query.json
+}
+
 resource "honeycombio_trigger" "trigger" {
   name        = "Requests are slower than usual"
   description = "Average duration of all requests for ThatSpecialTenant for the last 15 minutes."
 
   disabled = false
 
-  query_json = data.honeycombio_query.query.json
-  dataset    = var.dataset
+  query_id = honeycombio_query.trigger-query.id
+  dataset  = var.dataset
 
   frequency = 900 // in seconds, 15 minutes
 
diff --git a/example/trigger_with_slack_recipient/main.tf b/example/trigger_with_slack_recipient/main.tf
index f3297b41..e3ff55d4 100644
--- a/example/trigger_with_slack_recipient/main.tf
+++ b/example/trigger_with_slack_recipient/main.tf
@@ -10,7 +10,7 @@ variable "dataset" {
   type = string
 }
 
-data "honeycombio_query" "query" {
+data "honeycombio_query_specification" "query" {
   calculation {
     op     = "AVG"
     column = "duration_ms"
@@ -26,11 +26,16 @@ data "honeycombio_trigger_recipient" "slack" {
   target  = "#honeycombio"
 }
 
+resource "honeycombio_query" "trigger-query" {
+  dataset    = var.dataset
+  query_json = data.honeycombio_query_specification.query.json
+}
+
 resource "honeycombio_trigger" "trigger" {
   name = "Requests are slower than usual"
 
-  query_json = data.honeycombio_query.query.json
-  dataset    = var.dataset
+  query_id = honeycombio_query.trigger-query.id
+  dataset  = var.dataset
 
   threshold {
     op    = ">"
diff --git a/honeycombio/data_source_query.go b/honeycombio/data_source_query_specification.go
similarity index 96%
rename from honeycombio/data_source_query.go
rename to honeycombio/data_source_query_specification.go
index 9f29e18f..c76a6e30 100644
--- a/honeycombio/data_source_query.go
+++ b/honeycombio/data_source_query_specification.go
@@ -13,9 +13,9 @@ import (
 	"github.com/honeycombio/terraform-provider-honeycombio/honeycombio/internal/hashcode"
 )
 
-func dataSourceHoneycombioQuery() *schema.Resource {
+func dataSourceHoneycombioQuerySpec() *schema.Resource {
 	return &schema.Resource{
-		ReadContext: dataSourceHoneycombioQueryRead,
+		ReadContext: dataSourceHoneycombioQuerySpecRead,
 
 		Schema: map[string]*schema.Schema{
 			"calculation": {
@@ -79,9 +79,9 @@ func dataSourceHoneycombioQuery() *schema.Resource {
 				},
 			},
 			"filter_combination": {
-				Type:         schema.TypeString,
-				Optional:     true,
-				Default:      "AND",
+				Type:     schema.TypeString,
+				Optional: true,
+				// Note: API assumes 'AND' if not provided
 				ValidateFunc: validation.StringInSlice([]string{"AND", "OR"}, false),
 			},
 			"breakdowns": {
@@ -148,7 +148,7 @@ func dataSourceHoneycombioQuery() *schema.Resource {
 	}
 }
 
-func dataSourceHoneycombioQueryRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+func dataSourceHoneycombioQuerySpecRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
 	calculations, err := extractCalculations(d)
 	if err != nil {
 		return diag.FromErr(err)
diff --git a/honeycombio/internal/verify/json.go b/honeycombio/internal/verify/json.go
new file mode 100644
index 00000000..22d86052
--- /dev/null
+++ b/honeycombio/internal/verify/json.go
@@ -0,0 +1,36 @@
+package verify
+
+import (
+	"bytes"
+	"encoding/json"
+	"reflect"
+
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+)
+
+func SuppressEquivJSONDiffs(_, orig, new string, d *schema.ResourceData) bool {
+	oldBuf := bytes.NewBufferString("")
+	if err := json.Compact(oldBuf, []byte(orig)); err != nil {
+		return false
+	}
+
+	newBuf := bytes.NewBufferString("")
+	if err := json.Compact(newBuf, []byte(new)); err != nil {
+		return false
+	}
+
+	return JSONBytesEqual(oldBuf.Bytes(), newBuf.Bytes())
+}
+
+func JSONBytesEqual(b1, b2 []byte) bool {
+	var o1, o2 interface{}
+
+	if err := json.Unmarshal(b1, &o1); err != nil {
+		return false
+	}
+	if err := json.Unmarshal(b2, &o2); err != nil {
+		return false
+	}
+
+	return reflect.DeepEqual(o1, o2)
+}
diff --git a/honeycombio/provider.go b/honeycombio/provider.go
index 90d7fc6f..0960c4d1 100644
--- a/honeycombio/provider.go
+++ b/honeycombio/provider.go
@@ -44,9 +44,9 @@ func Provider() *schema.Provider {
 			},
 		},
 		DataSourcesMap: map[string]*schema.Resource{
-			"honeycombio_datasets":          dataSourceHoneycombioDatasets(),
-			"honeycombio_query":             dataSourceHoneycombioQuery(),
-			"honeycombio_trigger_recipient": dataSourceHoneycombioSlackRecipient(),
+			"honeycombio_datasets":            dataSourceHoneycombioDatasets(),
+			"honeycombio_query_specification": dataSourceHoneycombioQuerySpec(),
+			"honeycombio_trigger_recipient":   dataSourceHoneycombioSlackRecipient(),
 		},
 		ResourcesMap: map[string]*schema.Resource{
 			"honeycombio_board":            newBoard(),
diff --git a/honeycombio/resource_board.go b/honeycombio/resource_board.go
index 820a5993..1711a5cd 100644
--- a/honeycombio/resource_board.go
+++ b/honeycombio/resource_board.go
@@ -2,7 +2,6 @@ package honeycombio
 
 import (
 	"context"
-	"encoding/json"
 
 	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
 	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
@@ -54,10 +53,13 @@ func newBoard() *schema.Resource {
 							Type:     schema.TypeString,
 							Required: true,
 						},
-						"query_json": {
-							Type:             schema.TypeString,
-							Required:         true,
-							ValidateDiagFunc: validateQueryJSON(),
+						"query_id": {
+							Type:     schema.TypeString,
+							Required: true,
+						},
+						"query_annotation_id": {
+							Type:     schema.TypeString,
+							Optional: true,
 						},
 					},
 				},
@@ -95,15 +97,6 @@ func resourceBoardRead(ctx context.Context, d *schema.ResourceData, meta interfa
 		return diag.FromErr(err)
 	}
 
-	// API returns "" for filterCombination if set to the default value "AND"
-	// To keep the Terraform config simple, we'll explicitly set "AND" ourself
-	for i := range b.Queries {
-		q := &b.Queries[i]
-		if q.Query.FilterCombination == "" {
-			q.Query.FilterCombination = honeycombio.FilterCombinationAnd
-		}
-	}
-
 	d.SetId(b.ID)
 	d.Set("name", b.Name)
 	d.Set("description", b.Description)
@@ -112,16 +105,12 @@ func resourceBoardRead(ctx context.Context, d *schema.ResourceData, meta interfa
 	queries := make([]map[string]interface{}, len(b.Queries))
 
 	for i, q := range b.Queries {
-		queryJSON, err := encodeQuery(q.Query)
-		if err != nil {
-			return diag.FromErr(err)
-		}
-
 		queries[i] = map[string]interface{}{
-			"caption":     q.Caption,
-			"query_style": q.QueryStyle,
-			"dataset":     q.Dataset,
-			"query_json":  queryJSON,
+			"caption":             q.Caption,
+			"query_style":         q.QueryStyle,
+			"dataset":             q.Dataset,
+			"query_id":            q.QueryID,
+			"query_annotation_id": q.QueryAnnotationID,
 		}
 	}
 
@@ -164,17 +153,12 @@ func expandBoard(d *schema.ResourceData) (*honeycombio.Board, error) {
 	for _, q := range qs {
 		m := q.(map[string]interface{})
 
-		var query honeycombio.QuerySpec
-		err := json.Unmarshal([]byte(m["query_json"].(string)), &query)
-		if err != nil {
-			return nil, err
-		}
-
 		queries = append(queries, honeycombio.BoardQuery{
-			Caption:    m["caption"].(string),
-			QueryStyle: honeycombio.BoardQueryStyle(m["query_style"].(string)),
-			Dataset:    m["dataset"].(string),
-			Query:      &query,
+			Caption:           m["caption"].(string),
+			QueryStyle:        honeycombio.BoardQueryStyle(m["query_style"].(string)),
+			Dataset:           m["dataset"].(string),
+			QueryID:           m["query_id"].(string),
+			QueryAnnotationID: m["query_annotation_id"].(string),
 		})
 	}
 
diff --git a/honeycombio/resource_query.go b/honeycombio/resource_query.go
index 5b79a064..72cc1aed 100644
--- a/honeycombio/resource_query.go
+++ b/honeycombio/resource_query.go
@@ -7,6 +7,7 @@ import (
 	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
 	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
 	honeycombio "github.com/honeycombio/terraform-provider-honeycombio/client"
+	"github.com/honeycombio/terraform-provider-honeycombio/honeycombio/internal/verify"
 )
 
 func newQuery() *schema.Resource {
@@ -23,6 +24,7 @@ func newQuery() *schema.Resource {
 				Required:         true,
 				ForceNew:         true,
 				ValidateDiagFunc: validateQueryJSON(),
+				DiffSuppressFunc: verify.SuppressEquivJSONDiffs,
 			},
 			"dataset": {
 				Type:     schema.TypeString,
@@ -66,12 +68,10 @@ func resourceQueryRead(ctx context.Context, d *schema.ResourceData, meta interfa
 		return diag.FromErr(err)
 	}
 
+	// Track ID at the Resource level
 	d.SetId(*query.ID)
-	// Normalize the query before converting it to JSON to avoid unwanted diffs
 	query.ID = nil
-	if query.FilterCombination == "" {
-		query.FilterCombination = honeycombio.FilterCombinationAnd
-	}
+
 	queryJSON, err := encodeQuery(query)
 	if err != nil {
 		return diag.FromErr(err)
diff --git a/honeycombio/resource_trigger.go b/honeycombio/resource_trigger.go
index 7710d1c3..cdf051e6 100644
--- a/honeycombio/resource_trigger.go
+++ b/honeycombio/resource_trigger.go
@@ -2,7 +2,6 @@ package honeycombio
 
 import (
 	"context"
-	"encoding/json"
 	"fmt"
 	"strings"
 
@@ -40,16 +39,9 @@ func newTrigger() *schema.Resource {
 				Type:     schema.TypeBool,
 				Optional: true,
 			},
-			"query_json": {
+			"query_id": {
 				Type:     schema.TypeString,
 				Required: true,
-				ValidateDiagFunc: validateQueryJSON(func(q *honeycombio.QuerySpec) diag.Diagnostics {
-					err := honeycombio.MatchesTriggerSubset(q)
-					if err != nil {
-						return diag.FromErr(err)
-					}
-					return nil
-				}),
 			},
 			"threshold": {
 				Type:     schema.TypeList,
@@ -157,22 +149,11 @@ func resourceTriggerRead(ctx context.Context, d *schema.ResourceData, meta inter
 		return diag.FromErr(err)
 	}
 
-	// API returns nil for filterCombination if set to the default value "AND"
-	// To keep the Terraform config simple, we'll explicitly set "AND" ourself
-	if t.Query.FilterCombination == "" {
-		t.Query.FilterCombination = honeycombio.FilterCombinationAnd
-	}
-
 	d.SetId(t.ID)
 	d.Set("name", t.Name)
 	d.Set("description", t.Description)
 	d.Set("disabled", t.Disabled)
-
-	encodedQuery, err := encodeQuery(t.Query)
-	if err != nil {
-		return diag.FromErr(err)
-	}
-	d.Set("query_json", encodedQuery)
+	d.Set("query_id", t.QueryID)
 
 	err = d.Set("threshold", flattenTriggerThreshold(t.Threshold))
 	if err != nil {
@@ -220,19 +201,12 @@ func resourceTriggerDelete(ctx context.Context, d *schema.ResourceData, meta int
 }
 
 func expandTrigger(d *schema.ResourceData) (*honeycombio.Trigger, error) {
-	var query honeycombio.QuerySpec
-
-	err := json.Unmarshal([]byte(d.Get("query_json").(string)), &query)
-	if err != nil {
-		return nil, err
-	}
-
 	trigger := &honeycombio.Trigger{
 		ID:          d.Id(),
 		Name:        d.Get("name").(string),
 		Description: d.Get("description").(string),
 		Disabled:    d.Get("disabled").(bool),
-		Query:       &query,
+		QueryID:     d.Get("query_id").(string),
 		Threshold:   expandTriggerThreshold(d.Get("threshold").([]interface{})),
 		Frequency:   d.Get("frequency").(int),
 		Recipients:  expandTriggerRecipients(d.Get("recipient").([]interface{})),


Test patch:
diff --git a/client/trigger_test.go b/client/trigger_test.go
index 295f5f31..8f94cffe 100644
--- a/client/trigger_test.go
+++ b/client/trigger_test.go
@@ -70,6 +70,7 @@ func TestTriggers(t *testing.T) {
 
 		// copy IDs before asserting equality
 		data.ID = trigger.ID
+		data.QueryID = trigger.QueryID
 		for i := range trigger.Recipients {
 			data.Recipients[i].ID = trigger.Recipients[i].ID
 		}
@@ -98,6 +99,9 @@ func TestTriggers(t *testing.T) {
 
 		result, err := c.Triggers.Update(ctx, dataset, trigger)
 
+		// copy IDs before asserting equality
+		trigger.QueryID = result.QueryID
+
 		assert.NoError(t, err)
 		assert.Equal(t, trigger, result)
 	})
diff --git a/honeycombio/data_source_query_test.go b/honeycombio/data_source_query_specification_test.go
similarity index 86%
rename from honeycombio/data_source_query_test.go
rename to honeycombio/data_source_query_specification_test.go
index 88d202b5..5ae39feb 100644
--- a/honeycombio/data_source_query_test.go
+++ b/honeycombio/data_source_query_specification_test.go
@@ -24,7 +24,7 @@ func TestAccDataSourceHoneycombioQuery_basic(t *testing.T) {
 }
 
 const testAccQueryConfig = `
-data "honeycombio_query" "test" {
+data "honeycombio_query_specification" "test" {
     calculation {
         op     = "AVG"
         column = "duration_ms"
@@ -57,13 +57,13 @@ data "honeycombio_query" "test" {
     }
 
     limit 	    = 250
-	time_range  = 7200
-	start_time  = 1577836800
+    time_range  = 7200
+    start_time  = 1577836800
     granularity = 30
 }
 
 output "query_json" {
-    value = data.honeycombio_query.test.json
+    value = data.honeycombio_query_specification.test.json
 }`
 
 //Note: By default go encodes `<` and `>` for html, hence the `\u003e`
@@ -90,7 +90,6 @@ const expectedJSON string = `{
       "value": "ThatSpecialTenant"
     }
   ],
-  "filter_combination": "AND",
   "breakdowns": [
     "column_1"
   ],
@@ -126,7 +125,7 @@ func TestAccDataSourceHoneycombioQuery_validationChecks(t *testing.T) {
 var testStepsQueryValidationChecks_calculation = []resource.TestStep{
 	{
 		Config: `
-data "honeycombio_query" "test" {
+data "honeycombio_query_specification" "test" {
   calculation {
     op     = "COUNT"
     column = "we-should-not-specify-a-column-with-COUNT"
@@ -137,7 +136,7 @@ data "honeycombio_query" "test" {
 	},
 	{
 		Config: `
-data "honeycombio_query" "test" {
+data "honeycombio_query_specification" "test" {
   calculation {
     op     = "AVG"
   }
@@ -150,7 +149,7 @@ data "honeycombio_query" "test" {
 var testStepsQueryValidationChecks_filter = []resource.TestStep{
 	{
 		Config: `
-data "honeycombio_query" "test" {
+data "honeycombio_query_specification" "test" {
   filter {
     column = "column"
     op     = "exists"
@@ -162,7 +161,7 @@ data "honeycombio_query" "test" {
 	},
 	{
 		Config: `
-data "honeycombio_query" "test" {
+data "honeycombio_query_specification" "test" {
   filter {
     column = "column"
     op     = ">"
@@ -173,7 +172,7 @@ data "honeycombio_query" "test" {
 	},
 	{
 		Config: `
-data "honeycombio_query" "test" {
+data "honeycombio_query_specification" "test" {
   filter {
     column        = "column"
     op            = ">"
@@ -186,7 +185,7 @@ data "honeycombio_query" "test" {
 	},
 	{
 		Config: `
-data "honeycombio_query" "test" {
+data "honeycombio_query_specification" "test" {
   filter {
     column        = "column"
     op            = "in"
@@ -200,7 +199,7 @@ data "honeycombio_query" "test" {
 
 func testStepsQueryValidationChecks_limit() []resource.TestStep {
 	var queryLimitFmt = `
-data "honeycombio_query" "test" {
+data "honeycombio_query_specification" "test" {
   limit = %d
 }`
 	return []resource.TestStep{
@@ -222,7 +221,7 @@ data "honeycombio_query" "test" {
 var testStepsQueryValidationChecks_time = []resource.TestStep{
 	{
 		Config: `
-data "honeycombio_query" "test" {
+data "honeycombio_query_specification" "test" {
   time_range = 7200
   start_time = 1577836800
   end_time   = 1577844000
@@ -232,7 +231,7 @@ data "honeycombio_query" "test" {
 	},
 	{
 		Config: `
-data "honeycombio_query" "test" {
+data "honeycombio_query_specification" "test" {
   time_range  = 120
   granularity = 13
 }
@@ -241,7 +240,7 @@ data "honeycombio_query" "test" {
 	},
 	{
 		Config: `
-data "honeycombio_query" "test" {
+data "honeycombio_query_specification" "test" {
   time_range  = 60000
   granularity = 59
 }
@@ -259,15 +258,13 @@ func appendAllTestSteps(steps ...[]resource.TestStep) []resource.TestStep {
 }
 
 func TestAccDataSourceHoneycombioQuery_filterOpInAndNotIn(t *testing.T) {
-	dataset := testAccDataset()
-
 	resource.Test(t, resource.TestCase{
 		PreCheck:          testAccPreCheck(t),
 		ProviderFactories: testAccProviderFactories,
 		Steps: []resource.TestStep{
 			{
-				Config: fmt.Sprintf(`
-data "honeycombio_query" "test" {
+				Config: `
+data "honeycombio_query_specification" "test" {
   calculation {
     op = "COUNT"
   }
@@ -283,14 +280,7 @@ data "honeycombio_query" "test" {
     value  = "fzz,bzz"
   }
 }
-
-resource "honeycombio_board" "test" {
-  name = "terraform-provider-honeycombio - Test honeycombio-query - filter ops in/not-in"
-  query {
-    dataset    = "%v"
-    query_json = data.honeycombio_query.test.json
-  }
-}`, dataset),
+`,
 			},
 		},
 	})
diff --git a/honeycombio/resource_board_test.go b/honeycombio/resource_board_test.go
index c07190ac..b63585af 100644
--- a/honeycombio/resource_board_test.go
+++ b/honeycombio/resource_board_test.go
@@ -36,7 +36,7 @@ func TestAccHoneycombioBoard_basic(t *testing.T) {
 
 func testAccBoardConfig(dataset string) string {
 	return fmt.Sprintf(`
-data "honeycombio_query" "test" {
+data "honeycombio_query_specification" "test" {
   count = 2
 
   calculation {
@@ -51,22 +51,40 @@ data "honeycombio_query" "test" {
   }
 }
 
+resource "honeycombio_query" "test" {
+  count = 2
+
+  dataset    = "%s"
+  query_json = data.honeycombio_query_specification.test[count.index].json
+}
+
+resource "honeycombio_query_annotation" "test" {
+  count = 2
+
+  dataset     = "%s"
+  name        = "My annotated query"
+  description = "My lovely description"
+  query_id    = honeycombio_query.test[count.index].id
+}
+
 resource "honeycombio_board" "test" {
   name  = "Test board from terraform-provider-honeycombio"
   style = "list"
-     
+
   query {
-    caption = "test query 0"
-    dataset = "%s"
-    query_json = data.honeycombio_query.test[0].json
+    caption             = "test query 0"
+    dataset             = "%s"
+    query_id            = honeycombio_query.test[0].id
+    query_annotation_id = honeycombio_query_annotation.test[0].id
   }
   query {
-    caption     = "test query 1"
-    query_style = "combo"
-    dataset     = "%s"
-    query_json  = data.honeycombio_query.test[1].json
+    caption             = "test query 1"
+    query_style         = "combo"
+    dataset             = "%s"
+    query_id            = honeycombio_query.test[1].id
+    query_annotation_id = honeycombio_query_annotation.test[1].id
   }
-}`, dataset, dataset)
+}`, dataset, dataset, dataset, dataset)
 }
 
 func testAccCheckBoardExists(t *testing.T, name string) resource.TestCheckFunc {
@@ -89,10 +107,11 @@ func testAccCheckBoardExists(t *testing.T, name string) resource.TestCheckFunc {
 			Style:       honeycombio.BoardStyleList,
 			Queries: []honeycombio.BoardQuery{
 				{
-					Caption:    "test query 0",
-					QueryStyle: honeycombio.BoardQueryStyleGraph,
-					Dataset:    testAccDataset(),
-					QueryID:    createdBoard.Queries[0].QueryID,
+					Caption:           "test query 0",
+					QueryStyle:        honeycombio.BoardQueryStyleGraph,
+					Dataset:           testAccDataset(),
+					QueryID:           createdBoard.Queries[0].QueryID,
+					QueryAnnotationID: createdBoard.Queries[0].QueryAnnotationID,
 					Query: &honeycombio.QuerySpec{
 						Calculations: []honeycombio.CalculationSpec{
 							{
@@ -111,10 +130,11 @@ func testAccCheckBoardExists(t *testing.T, name string) resource.TestCheckFunc {
 					},
 				},
 				{
-					Caption:    "test query 1",
-					QueryStyle: honeycombio.BoardQueryStyleCombo,
-					Dataset:    testAccDataset(),
-					QueryID:    createdBoard.Queries[1].QueryID,
+					Caption:           "test query 1",
+					QueryStyle:        honeycombio.BoardQueryStyleCombo,
+					Dataset:           testAccDataset(),
+					QueryID:           createdBoard.Queries[1].QueryID,
+					QueryAnnotationID: createdBoard.Queries[1].QueryAnnotationID,
 					Query: &honeycombio.QuerySpec{
 						Calculations: []honeycombio.CalculationSpec{
 							{
diff --git a/honeycombio/resource_query_annotation_test.go b/honeycombio/resource_query_annotation_test.go
index 14fa750f..15739882 100644
--- a/honeycombio/resource_query_annotation_test.go
+++ b/honeycombio/resource_query_annotation_test.go
@@ -37,7 +37,7 @@ func TestAccHoneycombioQueryAnnotation_update(t *testing.T) {
 
 func testAccResourceQueryAnnotationConfig(dataset string, name string) string {
 	return fmt.Sprintf(`
-data "honeycombio_query" "test" {
+data "honeycombio_query_specification" "test" {
   calculation {
     op     = "AVG"
     column = "duration_ms"
@@ -52,7 +52,7 @@ data "honeycombio_query" "test" {
 
 resource "honeycombio_query" "test" {
   dataset = "%s"
-  query_json = data.honeycombio_query.test.json
+  query_json = data.honeycombio_query_specification.test.json
 }
 
 resource "honeycombio_query_annotation" "test" {
diff --git a/honeycombio/resource_query_test.go b/honeycombio/resource_query_test.go
index afa0a245..59bb4377 100644
--- a/honeycombio/resource_query_test.go
+++ b/honeycombio/resource_query_test.go
@@ -40,7 +40,7 @@ func TestAccHoneycombioQuery_update(t *testing.T) {
 
 func testAccResourceQueryConfig(dataset string, duration int) string {
 	return fmt.Sprintf(`
-data "honeycombio_query" "test" {
+data "honeycombio_query_specification" "test" {
   calculation {
     op     = "AVG"
     column = "duration_ms"
@@ -55,7 +55,7 @@ data "honeycombio_query" "test" {
 
 resource "honeycombio_query" "test" {
   dataset = "%s"
-  query_json = data.honeycombio_query.test.json
+  query_json = data.honeycombio_query_specification.test.json
 }
 `, duration, dataset)
 }
diff --git a/honeycombio/resource_trigger_test.go b/honeycombio/resource_trigger_test.go
index 2187455b..b53ced01 100644
--- a/honeycombio/resource_trigger_test.go
+++ b/honeycombio/resource_trigger_test.go
@@ -3,7 +3,6 @@ package honeycombio
 import (
 	"context"
 	"fmt"
-	"regexp"
 	"testing"
 
 	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
@@ -106,76 +105,9 @@ func TestAccHoneycombioTrigger_triggerRecipientById(t *testing.T) {
 	})
 }
 
-func TestAccHoneycombioTrigger_validationErrors(t *testing.T) {
-	dataset := testAccDataset()
-
-	resource.ParallelTest(t, resource.TestCase{
-		PreCheck:          testAccPreCheck(t),
-		ProviderFactories: testAccProviderFactories,
-		Steps: []resource.TestStep{
-			{
-				Config:      testAccTriggerConfigWithQuery(dataset, `{]`),
-				ExpectError: regexp.MustCompile("value of query_json is not a valid query specification"),
-			},
-			{
-				Config:      testAccTriggerConfigWithQuery(dataset, `{"calculations":"bar"}`),
-				ExpectError: regexp.MustCompile("value of query_json is not a valid query specification"),
-			},
-			{
-				Config: testAccTriggerConfigWithQuery(dataset, `
-{
-    "calculations": [
-        {"op": "COUNT"},
-        {"op": "AVG", "column": "duration_ms"}
-    ]
-}`),
-				ExpectError: regexp.MustCompile("a trigger query should contain exactly one calculation"),
-			},
-			{
-				Config: testAccTriggerConfigWithQuery(dataset, `
-{
-    "calculations": [
-        {"op": "HEATMAP"}
-    ]
-}`),
-				ExpectError: regexp.MustCompile("a trigger query may not contain a HEATMAP calculation"),
-			},
-			{
-				Config: testAccTriggerConfigWithQuery(dataset, `
-{
-    "calculations": [
-        {"op": "AVG", "column": "duration_ms"}
-    ],
-    "orders": [
-        {"column": "column_1"}
-    ]
-}`),
-				ExpectError: regexp.MustCompile("orders is not allowed in a trigger query"),
-			},
-			{
-				Config: testAccTriggerConfigWithQuery(dataset, `
-{
-    "calculations": [
-        {"op": "AVG", "column": "duration_ms"}
-    ],
-    "limit": 100
-}`),
-				ExpectError: regexp.MustCompile("limit is not allowed in a trigger query"),
-			},
-			{
-				ResourceName:      "honeycombio_trigger.test",
-				ImportStateId:     "someId",
-				ImportState:       true,
-				ImportStateVerify: true,
-				ExpectError:       regexp.MustCompile("invalid import ID, supplied ID must be written as <dataset>/<trigger ID>"),
-			},
-		},
-	})
-}
-
 func testAccTriggerConfigWithFrequency(dataset string, frequency int) string {
 	return fmt.Sprintf(`
-data "honeycombio_query" "test" {
+data "honeycombio_query_specification" "test" {
   calculation {
     op     = "AVG"
     column = "duration_ms"
@@ -183,11 +115,16 @@ data "honeycombio_query" "test" {
   time_range = 1200
 }
 
+resource "honeycombio_query" "test" {
+  dataset    = "%s"
+  query_json = data.honeycombio_query_specification.test.json
+}
+
 resource "honeycombio_trigger" "test" {
   name    = "Test trigger from terraform-provider-honeycombio"
   dataset = "%s"
 
-  query_json = data.honeycombio_query.test.json
+  query_id = honeycombio_query.test.id
 
   threshold {
     op    = ">"
@@ -205,51 +142,39 @@ resource "honeycombio_trigger" "test" {
     type   = "email"
     target = "bye@example.com"
   }
-}`, dataset, frequency)
+}`, dataset, dataset, frequency)
 }
 
 func testAccTriggerConfigWithCount(dataset string) string {
 	return fmt.Sprintf(`
-data "honeycombio_query" "test" {
+data "honeycombio_query_specification" "test" {
   calculation {
     op     = "COUNT"
   }
   time_range = 1200
 }
 
-resource "honeycombio_trigger" "test" {
-  name    = "Test trigger from terraform-provider-honeycombio"
-  dataset = "%s"
-
-  query_json = data.honeycombio_query.test.json
-
-  threshold {
-    op    = ">"
-    value = 100
-  }
-}`, dataset)
+resource "honeycombio_query" "test" {
+  dataset    = "%s"
+  query_json = data.honeycombio_query_specification.test.json
 }
 
-func testAccTriggerConfigWithQuery(dataset, query string) string {
-	return fmt.Sprintf(`
 resource "honeycombio_trigger" "test" {
   name    = "Test trigger from terraform-provider-honeycombio"
   dataset = "%s"
 
-  query_json = <<EOF
-%s
-EOF
-    
+  query_id = honeycombio_query.test.id
+
   threshold {
     op    = ">"
     value = 100
   }
-}`, dataset, query)
+}`, dataset, dataset)
 }
 
 func testAccTriggerConfigWithRecipientID(dataset, recipientID string) string {
 	return fmt.Sprintf(`
-data "honeycombio_query" "test" {
+data "honeycombio_query_specification" "test" {
   calculation {
     op     = "AVG"
     column = "duration_ms"
@@ -257,12 +182,17 @@ data "honeycombio_query" "test" {
   time_range = 1800
 }
 
+resource "honeycombio_query" "test" {
+  dataset    = "%s"
+  query_json = data.honeycombio_query_specification.test.json
+}
+
 resource "honeycombio_trigger" "test" {
   name    = "Test trigger from terraform-provider-honeycombio"
   dataset = "%s"
 
-  query_json = data.honeycombio_query.test.json
-        
+  query_id = honeycombio_query.test.id
+
   threshold {
     op    = ">"
     value = 100
@@ -271,5 +201,5 @@ resource "honeycombio_trigger" "test" {
   recipient {
     id = "%s"
   }
-}`, dataset, recipientID)
+}`, dataset, dataset, recipientID)
 }