Skip to content

Commit

Permalink
Merge pull request cyrilgdn#2 from SpencerBinXia/table-sequence-data-…
Browse files Browse the repository at this point in the history
…sources

Postgresql_tables and postgresql_sequences data sources
  • Loading branch information
SpencerBinXia authored Mar 4, 2022
2 parents 498741c + 8244aa2 commit d074dd6
Show file tree
Hide file tree
Showing 11 changed files with 775 additions and 56 deletions.
67 changes: 67 additions & 0 deletions postgresql/data_source_helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package postgresql

import (
"fmt"
"strings"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

const (
queryConcatKeywordWhere = "WHERE"
queryConcatKeywordAnd = "AND"
queryArrayKeywordAny = "ANY"
queryArrayKeywordAll = "ALL"
likePatternQuery = "LIKE"
notLikePatternQuery = "NOT LIKE"
regexPatternQuery = "~"
)

func applyOptionalPatternMatchingToQuery(query string, patternMatchingTarget string, queryConcatKeyword *string, d *schema.ResourceData) string {
likeAnyPatterns := d.Get("like_any_patterns").([]interface{})
likeAllPatterns := d.Get("like_all_patterns").([]interface{})
notLikeAllPatterns := d.Get("not_like_all_patterns").([]interface{})
regexPattern := d.Get("regex_pattern").(string)

if len(likeAnyPatterns) > 0 {
query = finalizeQueryWithPatternMatching(query, patternMatchingTarget, likePatternQuery, generatePatternArrayString(likeAnyPatterns, queryArrayKeywordAny), queryConcatKeyword)
}
if len(likeAllPatterns) > 0 {
query = finalizeQueryWithPatternMatching(query, patternMatchingTarget, likePatternQuery, generatePatternArrayString(likeAllPatterns, queryArrayKeywordAll), queryConcatKeyword)
}
if len(notLikeAllPatterns) > 0 {
query = finalizeQueryWithPatternMatching(query, patternMatchingTarget, notLikePatternQuery, generatePatternArrayString(notLikeAllPatterns, queryArrayKeywordAll), queryConcatKeyword)
}
if regexPattern != "" {
query = finalizeQueryWithPatternMatching(query, patternMatchingTarget, regexPatternQuery, fmt.Sprintf("'%s'", regexPattern), queryConcatKeyword)
}

return query
}

func generatePatternArrayString(patterns []interface{}, queryArrayKeyword string) string {
formattedPatterns := []string{}

for _, pattern := range patterns {
formattedPatterns = append(formattedPatterns, fmt.Sprintf("'%s'", pattern.(string)))
}
return fmt.Sprintf("%s (array[%s])", queryArrayKeyword, strings.Join(formattedPatterns, ","))
}

func applyEqualsAnyFilteringToQuery(query string, queryConcatKeyword *string, objectKeyword string, objects []interface{}) string {
if len(objects) > 0 {
query = fmt.Sprintf("%s %s %s = %s", query, *queryConcatKeyword, objectKeyword, generatePatternArrayString(objects, queryArrayKeywordAny))
*queryConcatKeyword = queryConcatKeywordAnd
}

return query
}

func finalizeQueryWithPatternMatching(query string, patternMatchingTarget string, additionalQuery string, pattern string, queryConcatKeyword *string) string {
finalizedQuery := fmt.Sprintf("%s %s %s %s %s", query, *queryConcatKeyword, patternMatchingTarget, additionalQuery, pattern)

//Set the query concatenation keyword from WHERE to AND if it has already been used.
*queryConcatKeyword = queryConcatKeywordAnd

return finalizedQuery
}
64 changes: 9 additions & 55 deletions postgresql/data_source_postgresql_schemas.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,17 @@ import (
var schemaQueries = map[string]string{
"query_include_system_schemas": `
SELECT schema_name
FROM information_schema.schemata s
FROM information_schema.schemata
`,
"query_exclude_system_schemas": `
SELECT schema_name
FROM information_schema.schemata s
WHERE s.schema_name NOT LIKE 'pg_%'
AND s.schema_name <> 'information_schema'
FROM information_schema.schemata
WHERE schema_name NOT LIKE 'pg_%'
AND schema_name <> 'information_schema'
`,
}

const queryArrayKeywordAny = "ANY"
const queryArrayKeywordAll = "ALL"
const schemaPatternMatchingTarget = "schema_name"

func dataSourcePostgreSQLDatabaseSchemas() *schema.Resource {
return &schema.Resource{
Expand Down Expand Up @@ -89,13 +88,16 @@ func dataSourcePostgreSQLSchemasRead(db *DBConnection, d *schema.ResourceData) e
includeSystemSchemas := d.Get("include_system_schemas").(bool)

var query string
var queryConcatKeyword string
if includeSystemSchemas {
query = schemaQueries["query_include_system_schemas"]
queryConcatKeyword = queryConcatKeywordWhere
} else {
query = schemaQueries["query_exclude_system_schemas"]
queryConcatKeyword = queryConcatKeywordAnd
}

query = applyOptionalPatternMatchingToQuery(query, !includeSystemSchemas, d)
query = applyOptionalPatternMatchingToQuery(query, schemaPatternMatchingTarget, &queryConcatKeyword, d)

rows, err := txn.Query(query)
if err != nil {
Expand All @@ -119,54 +121,6 @@ func dataSourcePostgreSQLSchemasRead(db *DBConnection, d *schema.ResourceData) e
return nil
}

func applyOptionalPatternMatchingToQuery(query string, queryContainsWhere bool, d *schema.ResourceData) string {
likeAnyPatterns := d.Get("like_any_patterns").([]interface{})
likeAllPatterns := d.Get("like_all_patterns").([]interface{})
notLikeAllPatterns := d.Get("not_like_all_patterns").([]interface{})
regexPattern := d.Get("regex_pattern").(string)

likePatternQuery := "s.schema_name LIKE"
notLikePatternQuery := "s.schema_name NOT LIKE"
regexPatternQuery := "s.schema_name ~"

if len(likeAnyPatterns) > 0 {
query = concatenateQueryWithPatternMatching(query, likePatternQuery, generatePatternArrayString(likeAnyPatterns, queryArrayKeywordAny), &queryContainsWhere)
}
if len(likeAllPatterns) > 0 {
query = concatenateQueryWithPatternMatching(query, likePatternQuery, generatePatternArrayString(likeAllPatterns, queryArrayKeywordAll), &queryContainsWhere)
}
if len(notLikeAllPatterns) > 0 {
query = concatenateQueryWithPatternMatching(query, notLikePatternQuery, generatePatternArrayString(notLikeAllPatterns, queryArrayKeywordAll), &queryContainsWhere)
}
if regexPattern != "" {
query = concatenateQueryWithPatternMatching(query, regexPatternQuery, fmt.Sprintf("'%s'", regexPattern), &queryContainsWhere)
}

return query
}

func generatePatternArrayString(patterns []interface{}, queryArrayKeyword string) string {
formattedPatterns := []string{}

for _, pattern := range patterns {
formattedPatterns = append(formattedPatterns, fmt.Sprintf("'%s'", pattern.(string)))
}
return fmt.Sprintf("%s (array[%s])", queryArrayKeyword, strings.Join(formattedPatterns, ","))

}

func concatenateQueryWithPatternMatching(query string, additionalQuery string, pattern string, queryContainsWhere *bool) string {
var keyword string
if *queryContainsWhere {
keyword = "AND"
} else {
keyword = "WHERE"
*queryContainsWhere = true
}

return fmt.Sprintf("%s %s %s %s", query, keyword, additionalQuery, pattern)
}

func generateDataSourceSchemasID(d *schema.ResourceData, databaseName string) string {
return strings.Join([]string{
databaseName, strconv.FormatBool(d.Get("include_system_schemas").(bool)),
Expand Down
140 changes: 140 additions & 0 deletions postgresql/data_source_postgresql_sequences.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package postgresql

import (
"fmt"
"strings"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

const (
sequenceQuery = `
SELECT sequence_name, sequence_schema, data_type
FROM information_schema.sequences
`
sequencePatternMatchingTarget = "sequence_name"
sequenceSchemaKeyword = "sequence_schema"
)

func dataSourcePostgreSQLDatabaseSequences() *schema.Resource {
return &schema.Resource{
Read: PGResourceFunc(dataSourcePostgreSQLSequencesRead),
Schema: map[string]*schema.Schema{
"database": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "The PostgreSQL database which will be queried for sequence names",
},
"schemas": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
MinItems: 0,
Description: "The PostgreSQL schema(s) which will be queried for sequence names. Queries all schemas in the database by default",
},
"like_any_patterns": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
MinItems: 0,
Description: "Expression(s) which will be pattern matched against sequence names in the query using the PostgreSQL LIKE ANY operator",
},
"like_all_patterns": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
MinItems: 0,
Description: "Expression(s) which will be pattern matched against sequence names in the query using the PostgreSQL LIKE ALL operator",
},
"not_like_all_patterns": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
MinItems: 0,
Description: "Expression(s) which will be pattern matched against sequence names in the query using the PostgreSQL NOT LIKE ALL operator",
},
"regex_pattern": {
Type: schema.TypeString,
Optional: true,
Description: "Expression which will be pattern matched against sequence names in the query using the PostgreSQL ~ (regular expression match) operator",
},
"sequences": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"object_name": {
Type: schema.TypeString,
Computed: true,
},
"schema_name": {
Type: schema.TypeString,
Computed: true,
},
"data_type": {
Type: schema.TypeString,
Computed: true,
},
},
},
Description: "The list of PostgreSQL sequence names retrieved by this data source. Note that this returns a set, so duplicate table names across different schemas will be consolidated.",
},
},
}
}

func dataSourcePostgreSQLSequencesRead(db *DBConnection, d *schema.ResourceData) error {
database := d.Get("database").(string)

txn, err := startTransaction(db.client, database)
if err != nil {
return err
}
defer deferredRollback(txn)

query := sequenceQuery
queryConcatKeyword := queryConcatKeywordWhere

query = applyEqualsAnyFilteringToQuery(query, &queryConcatKeyword, sequenceSchemaKeyword, d.Get("schemas").([]interface{}))
query = applyOptionalPatternMatchingToQuery(query, sequencePatternMatchingTarget, &queryConcatKeyword, d)

rows, err := txn.Query(query)
if err != nil {
return err
}
defer rows.Close()

sequences := make([]interface{}, 0)
for rows.Next() {
var object_name string
var schema_name string
var data_type string

if err = rows.Scan(&object_name, &schema_name, &data_type); err != nil {
return fmt.Errorf("could not scan sequence output for database: %w", err)
}

result := make(map[string]interface{})
result["object_name"] = object_name
result["schema_name"] = schema_name
result["data_type"] = data_type
sequences = append(sequences, result)
}

d.Set("sequences", sequences)
d.SetId(generateDataSourceSequencesID(d, database))

return nil
}

func generateDataSourceSequencesID(d *schema.ResourceData, databaseName string) string {
return strings.Join([]string{
databaseName,
generatePatternArrayString(d.Get("schemas").([]interface{}), queryArrayKeywordAny),
generatePatternArrayString(d.Get("like_any_patterns").([]interface{}), queryArrayKeywordAny),
generatePatternArrayString(d.Get("like_all_patterns").([]interface{}), queryArrayKeywordAll),
generatePatternArrayString(d.Get("not_like_all_patterns").([]interface{}), queryArrayKeywordAll),
d.Get("regex_pattern").(string),
}, "_")
}
Loading

0 comments on commit d074dd6

Please sign in to comment.