Pouvez-vous créer des vues dans Amazon Athena? décrit comment créer une vue à l'aide de l'interface utilisateur.
Je voudrais créer une vue AWS Athena par programmation, idéalement en utilisant Terraform (qui appelle CloudFormation).
J'ai suivi les étapes décrites ici: https://ujjwalbhardwaj.me/post/create-virtual-views-with-aws-glue-and-query-them-using-athena , cependant je rencontre un problème avec cela en ce que la vue devient viciée rapidement.
...._view' is stale; it must be re-created.
Le code terraform ressemble à ceci:
resource "aws_glue_catalog_table" "Adobe_session_view" {
database_name = "${var.database_name}"
name = "session_view"
table_type = "VIRTUAL_VIEW"
view_original_text = "/* Presto View: ${base64encode(data.template_file.query_file.rendered)} */"
view_expanded_text = "/* Presto View */"
parameters = {
presto_view = "true"
comment = "Presto View"
}
storage_descriptor {
ser_de_info {
name = "ParquetHiveSerDe"
serialization_library = "org.Apache.hadoop.Hive.ql.io.parquet.serde.ParquetHiveSerDe"
}
columns { name = "first_column" type = "string" }
columns { name = "second_column" type = "int" }
...
columns { name = "nth_column" type = "string" }
}
Une alternative que je serais heureux d'utiliser est l'AWS CLI, cependant aws athena [option]
ne fournit aucune option pour cela.
J'ai essayé:
Comme vous l'avez suggéré, il est certainement possible de créer une vue Athena par programmation via l'AWS CLI en utilisant le start-query-execution
. Comme vous l'avez souligné, cela vous oblige à fournir un emplacement S3 pour les résultats même si vous n'aurez pas besoin de vérifier le fichier (Athena mettra un fichier txt vide à l'emplacement pour une raison quelconque).
Voici un exemple:
$ aws athena start-query-execution --query-string "create view my_view as select * from my_table" --result-configuration "OutputLocation=s3://my-bucket/tmp" --query-execution-context "Database=my_database"
{
"QueryExecutionId": "1744ed2b-e111-4a91-80ea-bcb1eb1c9c25"
}
Vous pouvez éviter que le client spécifie un compartiment en créant un groupe de travail et en y définissant l'emplacement.
Vous pouvez vérifier si la création de votre vue a réussi en utilisant le get-query-execution
commande.
$ aws --region athena get-query-execution --query-execution-id bedf3eba-55b0-42de-9a7f-7c0ba71c6d9b
{
"QueryExecution": {
"QueryExecutionId": "1744ed2b-e111-4a91-80ea-bcb1eb1c9c25",
"Query": "create view my_view as select * from my_table",
"StatementType": "DDL",
"ResultConfiguration": {
"OutputLocation": "s3://my-bucket/tmp/1744ed2b-e111-4a91-80ea-bcb1eb1c9c25.txt"
},
"Status": {
"State": "SUCCEEDED",
"SubmissionDateTime": 1558744806.679,
"CompletionDateTime": 1558744807.312
},
"Statistics": {
"EngineExecutionTimeInMillis": 548,
"DataScannedInBytes": 0
},
"WorkGroup": "primary"
}
}
La création de vues par programmation dans Athena n'est pas documentée et non prise en charge, mais possible. Ce qui se passe en arrière-plan lorsque vous créez une vue à l'aide de StartQueryExecution
, c'est qu'Athena laisse Presto créer la vue, puis extrait la représentation interne de Presto et la place dans le catalogue Glue.
Le problème d'obsolescence provient généralement des colonnes dans les métadonnées Presto et les métadonnées Glue étant désynchronisées. Une vue Athena contient en réalité trois descriptions de la vue: la vue SQL, les colonnes et leurs types au format Glue et les colonnes et types au format Presto. Si l'un de ceux-ci se désynchronise, vous obtiendrez le "… est périmé; il doit être recréé". Erreur.
Voici les exigences d'une table Glue pour fonctionner comme une vue Athena:
TableType
doit être VIRTUAL_VIEW
Parameters
doit contenir presto_view: true
TableInput.ViewOriginalText
Doit contenir une vue Presto encodée (voir ci-dessous)StorageDescriptor.SerdeInfo
Doit être une carte videStorageDescriptor.Columns
Doit contenir toutes les colonnes définies par la vue, avec leurs typesLa partie délicate est la vue Presto encodée. Cette structure est créée par ce code: https://github.com/prestosql/presto/blob/27a1b0e304be841055b461e2c00490dae4e30a4e/presto-Hive/src/main/Java/io/prestosql/plugin/Hive/HiveUtil. L597-L6 , et c'est plus ou moins ce qu'il fait:
/* Presto View:
(Avec un espace après :
)*/
(Avec un espace avant *
)Le JSON qui décrit la vue ressemble à ceci:
catalog
qui doit avoir la valeur awsdatacatalog
.schema
qui doit être le nom de la base de données dans laquelle la vue est créée (c'est-à-dire qu'elle doit correspondre à la propriété DatabaseName
de la structure Glue environnante.name
et type
originalSql
avec la vue SQL réelle (n'incluant pas CREATE VIEW …
, Elle doit commencer par SELECT …
Ou WITH …
)Voici un exemple:
{
"catalog": "awsdatacatalog",
"schema": "some_database",
"columns": [
{"name": "col1", "type": "varchar"},
{"name": "col2", "type": "bigint"}
],
"originalSql": "SELECT col1, col2 FROM some_other_table"
}
Une mise en garde ici est que les types de colonnes sont presque, mais pas tout à fait, les mêmes que les noms dans Glue. Si Athena/Glue aurait string
la valeur dans ce JSON doit être varchar
. Si Athena/Glue utilise array<string>
, La valeur dans ce JSON doit être array(varchar)
, et struct<foo:int>
Devient row(foo int)
.
C'est assez compliqué, et mettre tout cela ensemble nécessite quelques manipulations et tests. Le moyen le plus simple de le faire fonctionner est de créer quelques vues et de décoder les instructions ci-dessus à l'envers pour voir à quoi elles ressemblent, puis essayez de le faire vous-même.
Mise à jour des exemples ci-dessus pour la syntaxe Terraform 0.12+ et ajout de la lecture des requêtes de vue à partir du système de fichiers:
resource "null_resource" "athena_views" {
for_each = {
for filename in fileset("${path.module}/athenaviews/", "**"):
replace(filename,"/","_") => file("${path.module}/athenaviews/${filename}")
}
provisioner "local-exec" {
command = <<EOF
aws athena start-query-execution \
--output json \
--query-string CREATE OR REPLACE VIEW ${each.key} AS ${each.value} \
--query-execution-context "Database=${var.athena_database}" \
--result-configuration "OutputLocation=s3://${aws_s3_bucket.my-bucket.bucket}"
EOF
}
provisioner "local-exec" {
when = "destroy"
command = <<EOF
aws athena start-query-execution \
--output json \
--query-string DROP VIEW IF EXISTS ${each.key} \
--query-execution-context "Database=${var.athena_database}" \
--result-configuration "OutputLocation=s3://${aws_s3_bucket.my-bucket.bucket}"
EOF
}
}
Notez également alors when= "destroy"
bloquer pour vous assurer que les vues sont supprimées lorsque votre pile est détruite.
Placez les fichiers texte avec une requête SELECT sous votre chemin de module sous un répertoire (athenaview/dans cet exemple), et il les récupérera et créera des vues. Cela créera des vues nommées subfolder_filename
, et détruisez-les si les fichiers sont supprimés.
Ajout à la réponse de Theo: Dans le fichier JSON encodé en base64, le type "chaîne" n'est pas valide lors de la définition des attributs cloumn! Écrivez toujours "varchar" à ce stade.
edit: Aussi "int" doit être déclaré comme "entier"!
Je suis allé avec la solution de Theo et cela a fonctionné en utilisant des modèles AWS Cloud Formation.
Je voulais juste ajouter un petit indice, qui peut vous faire économiser des heures de débogage. Je n'écris pas ceci en tant que commentaire, car je n'ai pas encore le droit de commenter. N'hésitez pas à copier et coller ceci dans la section commentaire de la réponse de Theo.
Pour compléter les réponses par JD D
et Theo
, en travaillant avec leurs solutions, nous avons trouvé comment appeler l'AWS Cli via terraform comme suit:
resource "null_resource" "athena_view" {
provisioner "local-exec" {
command = <<EOF
aws sts assume-role \
--output json \
--region my_region \
--role-arn arn:aws:iam::${var.account_number}:role/my_role \
--role-session-name create_my_view > /tmp/credentials.json
export AWS_SESSION_TOKEN=$(jq -r '.Credentials.SessionToken' /tmp/credentials.json)
export AWS_ACCESS_KEY_ID=$(jq -r '.Credentials.AccessKeyId' /tmp/credentials.json)
export AWS_SECRET_ACCESS_KEY=$(jq -r '.Credentials.SecretAccessKey' /tmp/credentials.json)
aws athena start-query-execution \
--output json \
--region my_region \
--query-string "CREATE OR REPLACE VIEW my_view AS SELECT * FROM my_table \
--query-execution-context "Database=${var.database_name}" \
--result-configuration "OutputLocation=s3://${aws_s3_bucket.my-bucket.bucket}"
EOF
}
}
Nous utilisons null_resource ... pour exécuter des provisionneurs qui ne sont pas directement associés à une ressource spécifique .
Le résultat de aws sts assume-role
est sorti en JSON dans /tmp/credentials.json
.
jq est utilisé pour analyser les champs nécessaires de la sortie de aws sts assume-role .
aws athena start-query-execution est alors capable de s'exécuter sous le rôle spécifié par les variables d'environnement définies.
Au lieu de --result-configuration "OutputLocation=s3://....
, --work-group
peut être spécifié, NOTEZ qu'il s'agit d'un indicateur distinct sur start-query-execution
, ne fait pas partie du --result-configuration
chaîne.
Sur la base des réponses précédentes, voici un exemple qui exécutera des requêtes uniquement si le fichier source a changé. De plus, au lieu de coller la requête SQL dans la commande, il utilise file://
adaptateur pour le transmettre à la commande AWS CLI.
resource "null_resource" "views" {
for_each = {
for filename in fileset("${var.sql_files_dir}/", "**/*.sql") :
replace(replace(filename, "/", "_"), ".sql", "") => "${var.sql_files_dir}/${filename}"
}
triggers = {
md5 = filemd5(each.value)
# External references from destroy provisioners are not allowed -
# they may only reference attributes of the related resource.
database_name = var.database_name
s3_bucket_query_output = var.s3_bucket_query_output
}
provisioner "local-exec" {
command = <<EOF
aws athena start-query-execution \
--output json \
--query-string file://${each.value} \
--query-execution-context "Database=${var.database_name}" \
--result-configuration "OutputLocation=s3://${var.s3_bucket_query_output}"
EOF
}
provisioner "local-exec" {
when = destroy
command = <<EOF
aws athena start-query-execution \
--output json \
--query-string 'DROP VIEW IF EXISTS ${each.key}' \
--query-execution-context "Database=${self.triggers.database_name}" \
--result-configuration "OutputLocation=s3://${self.triggers.s3_bucket_query_output}"
EOF
}
}
Pour que la destruction fonctionne correctement, nommez les fichiers exactement comme nom de fichier - example.sql
se rapporte à la requête:
CREATE OR REPLACE VIEW example AS ...