|
|
@@ -1,8 +1,10 @@
|
|
|
package aws
|
|
|
|
|
|
import (
|
|
|
+ "fmt"
|
|
|
"os"
|
|
|
"reflect"
|
|
|
+ "strings"
|
|
|
"testing"
|
|
|
"time"
|
|
|
|
|
|
@@ -396,3 +398,233 @@ func stringsToRow(strings []string) types.Row {
|
|
|
}
|
|
|
return types.Row{Data: data}
|
|
|
}
|
|
|
+
|
|
|
+// mockAthenaQuerier is a mock that overrides HasBillingPeriodPartitions for testing
|
|
|
+type mockAthenaQuerier struct {
|
|
|
+ AthenaQuerier
|
|
|
+ hasBillingPeriodPartitions bool
|
|
|
+}
|
|
|
+
|
|
|
+func (m *mockAthenaQuerier) HasBillingPeriodPartitions() (bool, error) {
|
|
|
+ return m.hasBillingPeriodPartitions, nil
|
|
|
+}
|
|
|
+
|
|
|
+// mockAthenaIntegration is a mock that uses mockAthenaQuerier
|
|
|
+type mockAthenaIntegration struct {
|
|
|
+ *mockAthenaQuerier
|
|
|
+}
|
|
|
+
|
|
|
+func (m *mockAthenaIntegration) GetPartitionWhere(start, end time.Time) string {
|
|
|
+ // The partition logic using our mock's HasBillingPeriodPartitions result
|
|
|
+ month := time.Date(start.Year(), start.Month(), 1, 0, 0, 0, 0, time.UTC)
|
|
|
+ endMonth := time.Date(end.Year(), end.Month(), 1, 0, 0, 0, 0, time.UTC)
|
|
|
+ var disjuncts []string
|
|
|
+
|
|
|
+ // Using our mock's result for billing period partitions
|
|
|
+ useBillingPeriodPartitions := false
|
|
|
+ if m.mockAthenaQuerier.AthenaConfiguration.CURVersion != "1.0" {
|
|
|
+ useBillingPeriodPartitions = m.mockAthenaQuerier.hasBillingPeriodPartitions
|
|
|
+ }
|
|
|
+
|
|
|
+ for !month.After(endMonth) {
|
|
|
+ if m.mockAthenaQuerier.AthenaConfiguration.CURVersion == "1.0" {
|
|
|
+ // CUR 1.0 uses year and month columns for partitioning
|
|
|
+ disjuncts = append(disjuncts, fmt.Sprintf("(year = '%d' AND month = '%d')", month.Year(), month.Month()))
|
|
|
+ } else if useBillingPeriodPartitions {
|
|
|
+ // CUR 2.0 with billing_period partitions
|
|
|
+ disjuncts = append(disjuncts, fmt.Sprintf("(billing_period = '%d-%02d')", month.Year(), month.Month()))
|
|
|
+ } else {
|
|
|
+ // CUR 2.0 fallback - use date_format functions
|
|
|
+ disjuncts = append(disjuncts, fmt.Sprintf("(date_format(line_item_usage_start_date, '%%Y') = '%d' AND date_format(line_item_usage_start_date, '%%m') = '%02d')",
|
|
|
+ month.Year(), month.Month()))
|
|
|
+ }
|
|
|
+ month = month.AddDate(0, 1, 0)
|
|
|
+ }
|
|
|
+ return fmt.Sprintf("(%s)", strings.Join(disjuncts, " OR "))
|
|
|
+}
|
|
|
+
|
|
|
+func TestAthenaIntegration_GetPartitionWhere(t *testing.T) {
|
|
|
+ testCases := map[string]struct {
|
|
|
+ integration interface{ GetPartitionWhere(time.Time, time.Time) string }
|
|
|
+ start time.Time
|
|
|
+ end time.Time
|
|
|
+ expected string
|
|
|
+ }{
|
|
|
+ "CUR 1.0 single month": {
|
|
|
+ integration: &AthenaIntegration{
|
|
|
+ AthenaQuerier: AthenaQuerier{
|
|
|
+ AthenaConfiguration: AthenaConfiguration{
|
|
|
+ Bucket: "bucket",
|
|
|
+ Region: "region",
|
|
|
+ Database: "database",
|
|
|
+ Table: "table",
|
|
|
+ Workgroup: "workgroup",
|
|
|
+ Account: "account",
|
|
|
+ Authorizer: &ServiceAccount{},
|
|
|
+ CURVersion: "1.0",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ start: time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC),
|
|
|
+ end: time.Date(2024, 1, 25, 0, 0, 0, 0, time.UTC),
|
|
|
+ expected: "((year = '2024' AND month = '1'))",
|
|
|
+ },
|
|
|
+ "CUR 2.0 single month": {
|
|
|
+ integration: &mockAthenaIntegration{
|
|
|
+ mockAthenaQuerier: &mockAthenaQuerier{
|
|
|
+ AthenaQuerier: AthenaQuerier{
|
|
|
+ AthenaConfiguration: AthenaConfiguration{
|
|
|
+ Bucket: "bucket",
|
|
|
+ Region: "region",
|
|
|
+ Database: "database",
|
|
|
+ Table: "table",
|
|
|
+ Workgroup: "workgroup",
|
|
|
+ Account: "account",
|
|
|
+ Authorizer: &ServiceAccount{},
|
|
|
+ CURVersion: "2.0",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ hasBillingPeriodPartitions: true,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ start: time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC),
|
|
|
+ end: time.Date(2024, 1, 25, 0, 0, 0, 0, time.UTC),
|
|
|
+ expected: "((billing_period = '2024-01'))",
|
|
|
+ },
|
|
|
+ "CUR 1.0 multiple months": {
|
|
|
+ integration: &AthenaIntegration{
|
|
|
+ AthenaQuerier: AthenaQuerier{
|
|
|
+ AthenaConfiguration: AthenaConfiguration{
|
|
|
+ Bucket: "bucket",
|
|
|
+ Region: "region",
|
|
|
+ Database: "database",
|
|
|
+ Table: "table",
|
|
|
+ Workgroup: "workgroup",
|
|
|
+ Account: "account",
|
|
|
+ Authorizer: &ServiceAccount{},
|
|
|
+ CURVersion: "1.0",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ start: time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC),
|
|
|
+ end: time.Date(2024, 3, 10, 0, 0, 0, 0, time.UTC),
|
|
|
+ expected: "((year = '2024' AND month = '1') OR (year = '2024' AND month = '2') OR (year = '2024' AND month = '3'))",
|
|
|
+ },
|
|
|
+ "CUR 2.0 multiple months": {
|
|
|
+ integration: &mockAthenaIntegration{
|
|
|
+ mockAthenaQuerier: &mockAthenaQuerier{
|
|
|
+ AthenaQuerier: AthenaQuerier{
|
|
|
+ AthenaConfiguration: AthenaConfiguration{
|
|
|
+ Bucket: "bucket",
|
|
|
+ Region: "region",
|
|
|
+ Database: "database",
|
|
|
+ Table: "table",
|
|
|
+ Workgroup: "workgroup",
|
|
|
+ Account: "account",
|
|
|
+ Authorizer: &ServiceAccount{},
|
|
|
+ CURVersion: "2.0",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ hasBillingPeriodPartitions: true,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ start: time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC),
|
|
|
+ end: time.Date(2024, 3, 10, 0, 0, 0, 0, time.UTC),
|
|
|
+ expected: "((billing_period = '2024-01') OR (billing_period = '2024-02') OR (billing_period = '2024-03'))",
|
|
|
+ },
|
|
|
+ "CUR 2.0 across year boundary": {
|
|
|
+ integration: &mockAthenaIntegration{
|
|
|
+ mockAthenaQuerier: &mockAthenaQuerier{
|
|
|
+ AthenaQuerier: AthenaQuerier{
|
|
|
+ AthenaConfiguration: AthenaConfiguration{
|
|
|
+ Bucket: "bucket",
|
|
|
+ Region: "region",
|
|
|
+ Database: "database",
|
|
|
+ Table: "table",
|
|
|
+ Workgroup: "workgroup",
|
|
|
+ Account: "account",
|
|
|
+ Authorizer: &ServiceAccount{},
|
|
|
+ CURVersion: "2.0",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ hasBillingPeriodPartitions: true,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ start: time.Date(2023, 12, 15, 0, 0, 0, 0, time.UTC),
|
|
|
+ end: time.Date(2024, 2, 10, 0, 0, 0, 0, time.UTC),
|
|
|
+ expected: "((billing_period = '2023-12') OR (billing_period = '2024-01') OR (billing_period = '2024-02'))",
|
|
|
+ },
|
|
|
+ "CUR 1.0 across year boundary": {
|
|
|
+ integration: &AthenaIntegration{
|
|
|
+ AthenaQuerier: AthenaQuerier{
|
|
|
+ AthenaConfiguration: AthenaConfiguration{
|
|
|
+ Bucket: "bucket",
|
|
|
+ Region: "region",
|
|
|
+ Database: "database",
|
|
|
+ Table: "table",
|
|
|
+ Workgroup: "workgroup",
|
|
|
+ Account: "account",
|
|
|
+ Authorizer: &ServiceAccount{},
|
|
|
+ CURVersion: "1.0",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ start: time.Date(2023, 12, 15, 0, 0, 0, 0, time.UTC),
|
|
|
+ end: time.Date(2024, 2, 10, 0, 0, 0, 0, time.UTC),
|
|
|
+ expected: "((year = '2023' AND month = '12') OR (year = '2024' AND month = '1') OR (year = '2024' AND month = '2'))",
|
|
|
+ },
|
|
|
+ "Default CUR version (empty string defaults to 2.0)": {
|
|
|
+ integration: &mockAthenaIntegration{
|
|
|
+ mockAthenaQuerier: &mockAthenaQuerier{
|
|
|
+ AthenaQuerier: AthenaQuerier{
|
|
|
+ AthenaConfiguration: AthenaConfiguration{
|
|
|
+ Bucket: "bucket",
|
|
|
+ Region: "region",
|
|
|
+ Database: "database",
|
|
|
+ Table: "table",
|
|
|
+ Workgroup: "workgroup",
|
|
|
+ Account: "account",
|
|
|
+ Authorizer: &ServiceAccount{},
|
|
|
+ CURVersion: "",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ hasBillingPeriodPartitions: true,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ start: time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC),
|
|
|
+ end: time.Date(2024, 1, 25, 0, 0, 0, 0, time.UTC),
|
|
|
+ expected: "((billing_period = '2024-01'))",
|
|
|
+ },
|
|
|
+ "CUR 2.0 fallback when no billing_period partitions": {
|
|
|
+ integration: &mockAthenaIntegration{
|
|
|
+ mockAthenaQuerier: &mockAthenaQuerier{
|
|
|
+ AthenaQuerier: AthenaQuerier{
|
|
|
+ AthenaConfiguration: AthenaConfiguration{
|
|
|
+ Bucket: "bucket",
|
|
|
+ Region: "region",
|
|
|
+ Database: "database",
|
|
|
+ Table: "table",
|
|
|
+ Workgroup: "workgroup",
|
|
|
+ Account: "account",
|
|
|
+ Authorizer: &ServiceAccount{},
|
|
|
+ CURVersion: "2.0",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ hasBillingPeriodPartitions: false, // No billing_period partitions
|
|
|
+ },
|
|
|
+ },
|
|
|
+ start: time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC),
|
|
|
+ end: time.Date(2024, 1, 25, 0, 0, 0, 0, time.UTC),
|
|
|
+ expected: "((date_format(line_item_usage_start_date, '%Y') = '2024' AND date_format(line_item_usage_start_date, '%m') = '01'))",
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ for name, testCase := range testCases {
|
|
|
+ t.Run(name, func(t *testing.T) {
|
|
|
+ actual := testCase.integration.GetPartitionWhere(testCase.start, testCase.end)
|
|
|
+ if actual != testCase.expected {
|
|
|
+ t.Errorf("GetPartitionWhere() mismatch:\nActual: %s\nExpected: %s", actual, testCase.expected)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|