├── .check.exs ├── .credo.exs ├── .formatter.exs ├── .github ├── dependabot.yml └── workflows │ └── elixir.yml ├── .gitignore ├── .tool-versions ├── .vscode └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── SECURITY.md ├── benchmarks └── bulk_create.exs ├── config └── config.exs ├── documentation ├── 1.0-CHANGELOG.md ├── dsls │ └── DSL-AshPostgres.DataLayer.md ├── topics │ ├── about-ash-postgres │ │ └── what-is-ash-postgres.md │ ├── advanced │ │ ├── expressions.md │ │ ├── manual-relationships.md │ │ ├── schema-based-multitenancy.md │ │ └── using-multiple-repos.md │ ├── development │ │ ├── migrations-and-tasks.md │ │ ├── testing.md │ │ └── upgrading-to-2.0.md │ └── resources │ │ ├── polymorphic-resources.md │ │ └── references.md └── tutorials │ ├── get-started-with-ash-postgres.md │ └── set-up-with-existing-database.md ├── lib ├── ash_postgres.ex ├── check_constraint.ex ├── custom_aggregate.ex ├── custom_extension.ex ├── custom_index.ex ├── data_layer.ex ├── data_layer │ └── info.ex ├── ecto_migration_default.ex ├── extensions │ └── vector.ex ├── functions │ ├── binding.ex │ ├── ilike.ex │ ├── like.ex │ ├── trigram_similarity.ex │ ├── vector_cosine_distance.ex │ └── vector_l2_distance.ex ├── igniter.ex ├── manual_relationship.ex ├── migration.ex ├── migration_compile_cache.ex ├── migration_generator │ ├── ash_functions.ex │ ├── migration_generator.ex │ ├── operation.ex │ └── phase.ex ├── mix │ ├── helpers.ex │ └── tasks │ │ ├── ash_postgres.create.ex │ │ ├── ash_postgres.drop.ex │ │ ├── ash_postgres.gen.resources.ex │ │ ├── ash_postgres.generate_migrations.ex │ │ ├── ash_postgres.install.ex │ │ ├── ash_postgres.migrate.ex │ │ ├── ash_postgres.rollback.ex │ │ ├── ash_postgres.setup_vector.ex │ │ └── ash_postgres.squash_snapshots.ex ├── multitenancy.ex ├── reference.ex ├── repo.ex ├── repo │ └── before_compile.ex ├── resource_generator │ ├── resource_generator.ex │ ├── sensitive_data.ex │ └── spec.ex ├── sql_implementation.ex ├── statement.ex ├── type.ex ├── types │ ├── ci_string_wrapper.ex │ ├── ltree.ex │ ├── string_wrapper.ex │ ├── timestamptz.ex │ ├── timestamptz_usec.ex │ ├── tsquery.ex │ └── tsvector.ex ├── verifiers │ ├── ensure_table_or_polymorphic.ex │ ├── prevent_attribute_multitenancy_and_non_full_match_type.ex │ ├── prevent_multidimensional_array_aggregates.ex │ ├── validate_identity_index_names.ex │ └── validate_references.ex └── version_agent.ex ├── logos └── small-logo.png ├── mix.exs ├── mix.lock ├── priv ├── dev_test_repo │ └── migrations │ │ ├── 20250526214825_migrate_resources_extensions_1.exs │ │ └── 20250526214827_migrate_resources1.exs ├── resource_snapshots │ ├── dev_test_repo │ │ ├── extensions.json │ │ └── multitenant_orgs │ │ │ └── 20250526214827.json │ ├── test_no_sandbox_repo │ │ └── extensions.json │ └── test_repo │ │ ├── accounts │ │ ├── 20221217123726.json │ │ └── 20240327211150.json │ │ ├── authors │ │ ├── 20220805191443.json │ │ ├── 20220914104733.json │ │ ├── 20240327211150.json │ │ └── 20240705113722.json │ │ ├── co_authored_posts │ │ └── 20241208221219.json │ │ ├── comedians │ │ ├── 20241217232254.json │ │ └── 20250413141328.json │ │ ├── comment_links │ │ └── 20250123161002.json │ │ ├── comment_ratings │ │ ├── 20220805191443.json │ │ └── 20240327211150.json │ │ ├── comments │ │ ├── 20220805191443.json │ │ ├── 20240327211150.json │ │ └── 20240327211917.json │ │ ├── complex_calculations_certifications │ │ ├── 20230816231942.json │ │ └── 20240327211150.json │ │ ├── complex_calculations_certifications_channel_members │ │ ├── 20231116013020.json │ │ ├── 20240327211150.json │ │ └── 20240327211917.json │ │ ├── complex_calculations_channels │ │ ├── 20231116013020.json │ │ ├── 20240327211150.json │ │ └── 20240327211917.json │ │ ├── complex_calculations_documentations │ │ ├── 20230816231942.json │ │ ├── 20240327211150.json │ │ └── 20240327211917.json │ │ ├── complex_calculations_skills │ │ ├── 20230816231942.json │ │ └── 20240327211150.json │ │ ├── content │ │ └── 20250123164209.json │ │ ├── content_visibility_group │ │ └── 20250123164209.json │ │ ├── csv │ │ └── 20250320225052.json │ │ ├── entities │ │ ├── 20240109160153.json │ │ ├── 20240327211150.json │ │ └── 20240327211917.json │ │ ├── extensions.json │ │ ├── integer_posts │ │ └── 20220805191443.json │ │ ├── items │ │ ├── 20240713134055.json │ │ ├── 20240717104854.json │ │ └── 20240717153736.json │ │ ├── jokes │ │ ├── 20241217232254.json │ │ └── 20250413141328.json │ │ ├── managers │ │ ├── 20230526144249.json │ │ └── 20240327211150.json │ │ ├── multitenant_named_orgs │ │ └── 20250519103535.json │ │ ├── multitenant_orgs │ │ ├── 20220805191443.json │ │ ├── 20240327211150.json │ │ ├── 20240627223225.json │ │ ├── 20240702164513.json │ │ └── 20240703155134.json │ │ ├── non_multitenant_post_links │ │ └── 20250122190558.json │ │ ├── note │ │ └── 20250123164209.json │ │ ├── orgs │ │ ├── 20230129050950.json │ │ ├── 20240327211150.json │ │ └── 20250210191116.json │ │ ├── other_items │ │ ├── 20240713134055.json │ │ └── 20240717151815.json │ │ ├── points │ │ └── 20250313112823.json │ │ ├── post_followers │ │ ├── 20240227180858.json │ │ ├── 20240227181137.json │ │ ├── 20240327211150.json │ │ ├── 20240516205244.json │ │ └── 20240517223946.json │ │ ├── post_links │ │ ├── 20220805191443.json │ │ ├── 20221017133955.json │ │ ├── 20221202194704.json │ │ ├── 20240610195853.json │ │ └── 20240617193218.json │ │ ├── post_permalinks │ │ └── 20240906170759.json │ │ ├── post_ratings │ │ ├── 20220805191443.json │ │ └── 20240327211150.json │ │ ├── post_views │ │ ├── 20230905050351.json │ │ └── 20240327211917.json │ │ ├── posts │ │ ├── 20220805191443.json │ │ ├── 20221125171150.json │ │ ├── 20221125171204.json │ │ ├── 20230129050950.json │ │ ├── 20230823161017.json │ │ ├── 20231127215636.json │ │ ├── 20231129141453.json │ │ ├── 20231219132807.json │ │ ├── 20240129221511.json │ │ ├── 20240224001913.json │ │ ├── 20240327211150.json │ │ ├── 20240327211917.json │ │ ├── 20240503012410.json │ │ ├── 20240504185511.json │ │ ├── 20240524031113.json │ │ ├── 20240524041750.json │ │ ├── 20240617193218.json │ │ ├── 20240618102809.json │ │ ├── 20240712232026.json │ │ ├── 20240715135403.json │ │ ├── 20240910180107.json │ │ ├── 20240911225320.json │ │ ├── 20240918104740.json │ │ ├── 20240929121224.json │ │ ├── 20250217054207.json │ │ ├── 20250313112823.json │ │ ├── 20250520130634.json │ │ └── 20250521105654.json │ │ ├── profile │ │ └── 20220805191443.json │ │ ├── profiles.profile │ │ └── 20240327211150.json │ │ ├── punchlines │ │ └── 20250413141328.json │ │ ├── records │ │ ├── 20240109160153.json │ │ ├── 20240327211150.json │ │ └── 20240327211917.json │ │ ├── records_temp_entities │ │ └── 20250605230457.json │ │ ├── relationship_items │ │ └── 20240717153736.json │ │ ├── schematic_groups │ │ └── 20240821213522.json │ │ ├── staff_group │ │ └── 20250123164209.json │ │ ├── staff_group_member │ │ └── 20250123164209.json │ │ ├── standup_clubs │ │ └── 20250413141328.json │ │ ├── stateful_post_followers │ │ └── 20240618085942.json │ │ ├── string_points │ │ └── 20250313112823.json │ │ ├── sub_items │ │ └── 20240713134055.json │ │ ├── subquery_access │ │ └── 20240130133933.json │ │ ├── subquery_child │ │ └── 20240130133933.json │ │ ├── subquery_parent │ │ └── 20240130133933.json │ │ ├── subquery_through │ │ └── 20240130133933.json │ │ ├── temp.temp_entities │ │ ├── 20240327211150.json │ │ └── 20240327211917.json │ │ ├── temp_entities │ │ └── 20240109160153.json │ │ ├── tenants │ │ ├── composite_key │ │ │ ├── 20250220073135.json │ │ │ └── 20250220073141.json │ │ ├── cross_tenant_post_links │ │ │ └── 20250122203454.json │ │ ├── friend_links │ │ │ └── 20240610162043.json │ │ └── multitenant_posts │ │ │ ├── 20220805191441.json │ │ │ └── 20240327211149.json │ │ ├── user_invites │ │ └── 20240727145758.json │ │ └── users │ │ ├── 20220805191443.json │ │ ├── 20221217123726.json │ │ ├── 20230129050950.json │ │ ├── 20240327211150.json │ │ ├── 20240727145758.json │ │ ├── 20240929124728.json │ │ ├── 20250320225052.json │ │ └── 20250321142835.json ├── test_no_sandbox_repo │ └── migrations │ │ ├── .gitkeep │ │ ├── 20240627223224_install_5_extensions.exs │ │ ├── 20240712232025_install_ash-functions_extension_4.exs │ │ └── 20250113205301_migrate_resources_extensions_1.exs └── test_repo │ ├── migrations │ ├── 20220805191440_install_4_extensions.exs │ ├── 20220805191443_migrate_resources1.exs │ ├── 20220914104733_migrate_resources2.exs │ ├── 20221017133955_migrate_resources3.exs │ ├── 20221125171148_migrate_resources4.exs │ ├── 20221125171150_migrate_resources5.exs │ ├── 20221202194704_migrate_resources6.exs │ ├── 20221217123726_migrate_resources7.exs │ ├── 20230129050950_migrate_resources8.exs │ ├── 20230526144249_migrate_resources9.exs │ ├── 20230712182523_install_ash-functions_extension.exs │ ├── 20230804223759_install_demo-functions_v0_extension.exs │ ├── 20230804223818_install_demo-functions_v1_extension.exs │ ├── 20230816231942_add_complex_calculation_tables.exs │ ├── 20230823161017_migrate_resources10.exs │ ├── 20230905050351_add_post_views.exs │ ├── 20231116013020_add_complex_calculations_channels.exs │ ├── 20231127212608_add_composite_type.exs │ ├── 20231127215636_migrate_resources11.exs │ ├── 20231129141453_migrate_resources12.exs │ ├── 20231214220937_install_ash-functions_extension_2.exs │ ├── 20231219132807_migrate_resources13.exs │ ├── 20231231051611_install_ash-functions_extension_3.exs │ ├── 20240109155951_create_temp_schema.exs │ ├── 20240109160153_migrate_resources14.exs │ ├── 20240129221511_migrate_resources15.exs │ ├── 20240130133933_add_resources_for_subquery_test.exs │ ├── 20240224001913_migrate_resources16.exs │ ├── 20240227180858_migrate_resources17.exs │ ├── 20240227181137_migrate_resources18.exs │ ├── 20240229050455_install_5_extensions.exs │ ├── 20240327211150_migrate_resources19.exs │ ├── 20240327211917_migrate_resources20.exs │ ├── 20240503012410_migrate_resources21.exs │ ├── 20240504185511_migrate_resources22.exs │ ├── 20240516205244_migrate_resources23.exs │ ├── 20240517223946_migrate_resources24.exs │ ├── 20240524031113_migrate_resources25.exs │ ├── 20240524041750_migrate_resources26.exs │ ├── 20240610195853_migrate_resources27.exs │ ├── 20240617193218_migrate_resources28.exs │ ├── 20240618085942_migrate_resources29.exs │ ├── 20240618102809_migrate_resources30.exs │ ├── 20240622192715_install_ash-functions_extension_4.exs │ ├── 20240627223225_migrate_resources31.exs │ ├── 20240703155134_migrate_resources32.exs │ ├── 20240705113722_migrate_resources33.exs │ ├── 20240712232026_migrate_resources34.exs │ ├── 20240713134055_multi_domain_calculations.exs │ ├── 20240715135403_migrate_resources35.exs │ ├── 20240717104854_no_attributes_calculation_test.exs │ ├── 20240717151815_migrate_resources36.exs │ ├── 20240717153736_migrate_resources37.exs │ ├── 20240727145758_user_invites.exs │ ├── 20240906170759_migrate_resources38.exs │ ├── 20240910180107_migrate_resources39.exs │ ├── 20240911225319_install_1_extensions.exs │ ├── 20240911225320_migrate_resources40.exs │ ├── 20240918104740_migrate_resources41.exs │ ├── 20240929121224_migrate_resources42.exs │ ├── 20240929124728_migrate_resources43.exs │ ├── 20241208221219_migrate_resources44.exs │ ├── 20241217232254_migrate_resources45.exs │ ├── 20250113205259_migrate_resources_extensions_1.exs │ ├── 20250122190558_migrate_resources46.exs │ ├── 20250123161002_migrate_resources47.exs │ ├── 20250123164209_migrate_resources48.exs │ ├── 20250210191116_migrate_resources49.exs │ ├── 20250217054207_migrate_resources50.exs │ ├── 20250313112823_migrate_resources51.exs │ ├── 20250320225052_add_csv_resource.exs │ ├── 20250321142835_migrate_resources52.exs │ ├── 20250413141328_add_punchlines_and_standup_clubs.exs │ ├── 20250519103535_migrate_resources53.exs │ ├── 20250520130634_migrate_resources54.exs │ ├── 20250521105654_add_model_tuple_to_post.exs │ └── 20250605230457_create_record_temp_entities_table.exs │ └── tenant_migrations │ ├── 20220805191441_migrate_resources1.exs │ ├── 20240327211149_migrate_resources2.exs │ ├── 20240610162043_migrate_resources3.exs │ ├── 20250122203454_migrate_resources4.exs │ ├── 20250220073135_migrate_resources5.exs │ └── 20250220073141_migrate_resources6.exs ├── test ├── aggregate_test.exs ├── ash_postgres_test.exs ├── atomics_test.exs ├── bulk_create_test.exs ├── bulk_destroy_test.exs ├── bulk_update_test.exs ├── calculation_test.exs ├── cascade_destroy_test.exs ├── combination_test.exs ├── complex_calculations_test.exs ├── composite_type_test.exs ├── constraint_test.exs ├── create_test.exs ├── custom_expression_test.exs ├── custom_index_test.exs ├── cve │ └── empty_atomic_non_bulk_actions_policy_bypass_test.exs ├── destroy_test.exs ├── dev_migrations_test.exs ├── distinct_test.exs ├── ecto_compatibility_test.exs ├── embeddable_resource_test.exs ├── enum_test.exs ├── error_expr_test.exs ├── filter_child_relationship_by_parent_relationship_test.exs ├── filter_field_policy_test.exs ├── filter_test.exs ├── load_test.exs ├── lock_test.exs ├── ltree_test.exs ├── manual_relationships_test.exs ├── manual_update_test.exs ├── many_to_many_expr_test.exs ├── migration_generator_test.exs ├── mix │ └── tasks │ │ └── ash_postgres.install_test.exs ├── mix_squash_snapshots_test.exs ├── multi_domain_calculations_test.exs ├── multitenancy_test.exs ├── polymorphism_test.exs ├── primary_key_test.exs ├── references_test.exs ├── rel_with_parent_filter_test.exs ├── resource_generator_test.exs ├── schema_test.exs ├── select_test.exs ├── sort_test.exs ├── storage_types_test.exs ├── subquery_test.exs ├── support │ ├── complex_calculations │ │ ├── domain.ex │ │ └── resources │ │ │ ├── certification.ex │ │ │ ├── channel.ex │ │ │ ├── channel_member.ex │ │ │ ├── dm_channel.ex │ │ │ ├── documentation.ex │ │ │ └── skill.ex │ ├── concat.ex │ ├── dev_test_repo.ex │ ├── domain.ex │ ├── multi_domain_calculations │ │ ├── domain_one.ex │ │ ├── domain_one │ │ │ └── item.ex │ │ ├── domain_three.ex │ │ ├── domain_three │ │ │ └── relationship_item.ex │ │ ├── domain_two.ex │ │ └── domain_two │ │ │ ├── other_item.ex │ │ │ └── sub_item.ex │ ├── multitenancy │ │ ├── domain.ex │ │ └── resources │ │ │ ├── composite_key_post.ex │ │ │ ├── cross_tenant_post_link.ex │ │ │ ├── dev_migrations_org.ex │ │ │ ├── named_org.ex │ │ │ ├── non_multitenant_post_link.ex │ │ │ ├── org.ex │ │ │ ├── post.ex │ │ │ ├── post_link.ex │ │ │ └── user.ex │ ├── relationships │ │ └── comments_containing_title.ex │ ├── repo_case.ex │ ├── resources │ │ ├── account.ex │ │ ├── author.ex │ │ ├── bio.ex │ │ ├── co_authored_post.ex │ │ ├── comedian.ex │ │ ├── comment.ex │ │ ├── comment_link.ex │ │ ├── content.ex │ │ ├── content_visibility_group.ex │ │ ├── csv.ex │ │ ├── db_point.ex │ │ ├── db_string_point.ex │ │ ├── entity.ex │ │ ├── integer_post.ex │ │ ├── invite.ex │ │ ├── joke.ex │ │ ├── manager.ex │ │ ├── note.ex │ │ ├── organization.ex │ │ ├── permalink.ex │ │ ├── post.ex │ │ ├── post_follower.ex │ │ ├── post_link.ex │ │ ├── post_views.ex │ │ ├── post_with_empty_update.ex │ │ ├── profile.ex │ │ ├── punchline.ex │ │ ├── rating.ex │ │ ├── record.ex │ │ ├── record_temp_entity.ex │ │ ├── role.ex │ │ ├── staff_group.ex │ │ ├── staff_group_member.ex │ │ ├── standup_club.ex │ │ ├── stateful_post_follwer.ex │ │ ├── subquery │ │ │ ├── access.ex │ │ │ ├── child.ex │ │ │ ├── child_domain.ex │ │ │ ├── parent.ex │ │ │ ├── parent_domain.ex │ │ │ └── through.ex │ │ ├── temp_entity.ex │ │ └── user.ex │ ├── string_agg.ex │ ├── test_app.ex │ ├── test_custom_extension.ex │ ├── test_no_sandbox_repo.ex │ ├── test_repo.ex │ ├── trigram_word_similarity.ex │ └── types │ │ ├── composite_point.ex │ │ ├── email.ex │ │ ├── money.ex │ │ ├── person_detail.ex │ │ ├── point.ex │ │ ├── status.ex │ │ ├── status_enum.ex │ │ ├── status_enum_no_cast.ex │ │ └── string_point.ex ├── test_helper.exs ├── transaction_test.exs ├── tuple_test.exs ├── type_test.exs ├── unique_identity_test.exs ├── update_test.exs └── upsert_test.exs └── usage-rules.md /.check.exs: -------------------------------------------------------------------------------- 1 | [ 2 | ## all available options with default values (see `mix check` docs for description) 3 | # parallel: true, 4 | # skipped: true, 5 | retry: false, 6 | ## list of tools (see `mix check` docs for defaults) 7 | tools: [ 8 | ## curated tools may be disabled (e.g. the check for compilation warnings) 9 | # {:compiler, false}, 10 | 11 | ## ...or adjusted (e.g. use one-line formatter for more compact credo output) 12 | # {:credo, "mix credo --format oneline"}, 13 | 14 | {:check_formatter, command: "mix spark.formatter --check"}, 15 | {:check_migrations, command: "mix test.check_migrations"} 16 | ## custom new tools may be added (mix tasks or arbitrary commands) 17 | # {:my_mix_task, command: "mix release", env: %{"MIX_ENV" => "prod"}}, 18 | # {:my_arbitrary_tool, command: "npm test", cd: "assets"}, 19 | # {:my_arbitrary_script, command: ["my_script", "argument with spaces"], cd: "scripts"} 20 | ] 21 | ] 22 | -------------------------------------------------------------------------------- /.formatter.exs: -------------------------------------------------------------------------------- 1 | spark_locals_without_parens = [ 2 | all_tenants?: 1, 3 | base_filter_sql: 1, 4 | calculations_to_sql: 1, 5 | check: 1, 6 | check_constraint: 2, 7 | check_constraint: 3, 8 | code?: 1, 9 | concurrently: 1, 10 | create?: 1, 11 | deferrable: 1, 12 | down: 1, 13 | error_fields: 1, 14 | exclusion_constraint_names: 1, 15 | foreign_key_names: 1, 16 | identity_index_names: 1, 17 | identity_wheres_to_sql: 1, 18 | ignore?: 1, 19 | include: 1, 20 | index: 1, 21 | index: 2, 22 | index?: 1, 23 | match_type: 1, 24 | match_with: 1, 25 | message: 1, 26 | migrate?: 1, 27 | migration_defaults: 1, 28 | migration_ignore_attributes: 1, 29 | migration_types: 1, 30 | name: 1, 31 | nulls_distinct: 1, 32 | on_delete: 1, 33 | on_update: 1, 34 | polymorphic?: 1, 35 | polymorphic_on_delete: 1, 36 | polymorphic_on_update: 1, 37 | prefix: 1, 38 | reference: 1, 39 | reference: 2, 40 | repo: 1, 41 | schema: 1, 42 | simple_join_first_aggregates: 1, 43 | skip_unique_indexes: 1, 44 | statement: 1, 45 | statement: 2, 46 | storage_types: 1, 47 | table: 1, 48 | template: 1, 49 | unique: 1, 50 | unique_index_names: 1, 51 | up: 1, 52 | update?: 1, 53 | using: 1, 54 | where: 1 55 | ] 56 | 57 | [ 58 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"], 59 | locals_without_parens: spark_locals_without_parens, 60 | export: [ 61 | locals_without_parens: spark_locals_without_parens 62 | ] 63 | ] 64 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: mix 4 | directory: "/" 5 | versioning-strategy: lockfile-only 6 | schedule: 7 | interval: weekly 8 | day: thursday 9 | groups: 10 | production-dependencies: 11 | dependency-type: production 12 | dev-dependencies: 13 | dependency-type: development 14 | -------------------------------------------------------------------------------- /.github/workflows/elixir.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | tags: 5 | - "v*" 6 | branches: [main] 7 | pull_request: 8 | branches: [main] 9 | jobs: 10 | ash-ci: 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | postgres-version: ["14", "15", "16"] 15 | uses: ash-project/ash/.github/workflows/ash-ci.yml@main 16 | with: 17 | postgres: true 18 | postgres-version: ${{ matrix.postgres-version }} 19 | publish-docs: ${{ matrix.postgres-version == '16' }} 20 | release: ${{ matrix.postgres-version == '16' }} 21 | igniter-upgrade: ${{matrix.postgres-version == '16'}} 22 | 23 | secrets: 24 | hex_api_key: ${{ secrets.HEX_API_KEY }} 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | ash_postgres-*.tar 24 | 25 | test_migration_path 26 | test_snapshots_path 27 | test_tenant_migration_path 28 | 29 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | erlang 27.1.2 2 | elixir 1.18.4 -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "citext", 4 | "mapset", 5 | "strpos" 6 | ] 7 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Zachary Scott Daniel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | This is a copy of the security policy in the core Ash repo. That is the authoritative source. 4 | 5 | [![OpenSSF Vulnerability Disclosure](https://img.shields.io/badge/OpenSSF-Vulnerability_Disclosure-green)](https://github.com/ossf/oss-vulnerability-guide/blob/main/finder-guide.md) 6 | [![GitHub Report](https://img.shields.io/badge/GitHub-Security_Advisories-blue)](https://github.com/ash-project/ash/security/advisories/new) 7 | [![Email Report](https://img.shields.io/badge/Email-security%40ash--hq.org-blue)](mailto:security@ash-hq.org) 8 | 9 | This repository follows the [OpenSSF Vulnerability Disclosure guide](https://github.com/ossf/oss-vulnerability-guide/tree/main). You can learn more about it in the [Finders Guide](https://github.com/ossf/oss-vulnerability-guide/blob/main/finder-guide.md). 10 | 11 | Please report vulnerabilities via the [GitHub Security Vulnerability Reporting](https://github.com/ash-project/ash/security/advisories/new) 12 | or via email to [`security@ash-hq.org`](mailto:security@ash-hq.org) if this does not work for you. 13 | 14 | Someone from the core team respond within 3 working days of your 15 | report. If the issue is confirmed as a vulnerability, we will open a Security 16 | Advisory. This project follows a 90 day disclosure timeline. 17 | 18 | If you have questions about reporting security issues, email the vulnerability 19 | management team: [`security@erlef.org`](mailto:security@erlef.org) 20 | -------------------------------------------------------------------------------- /documentation/topics/about-ash-postgres/what-is-ash-postgres.md: -------------------------------------------------------------------------------- 1 | # What is AshPostgres? 2 | 3 | AshPostgres is the PostgreSQL `Ash.DataLayer` for [Ash Framework](https://hexdocs.pm/ash). This is the most fully-featured Ash data layer, and unless you need a specific characteristic or feature of another data layer, you should use `AshPostgres`. 4 | 5 | > ### What versions are supported? {: .info} 6 | > 7 | > Any version higher than 13 is fully supported. Versions lower than this can be made to work, but certain edge cases may need to be manually handled. This becomes more and more true the further back in versions that you go. 8 | 9 | Use this to persist records in a PostgreSQL table or view. For example, the resource below would be persisted in a table called `tweets`: 10 | 11 | ```elixir 12 | defmodule MyApp.Tweet do 13 | use Ash.Resource, 14 | data_layer: AshPostgres.DataLayer 15 | 16 | attributes do 17 | integer_primary_key :id 18 | attribute :text, :string 19 | end 20 | 21 | relationships do 22 | belongs_to :author, MyApp.User 23 | end 24 | 25 | postgres do 26 | table "tweets" 27 | repo MyApp.Repo 28 | end 29 | end 30 | ``` 31 | 32 | The table might look like this: 33 | 34 | | id | text | author_id | 35 | | --- | --------------- | --------- | 36 | | 1 | "Hello, world!" | 1 | 37 | 38 | Creating records would add to the table, destroying records would remove from the table, and updating records would update the table. 39 | -------------------------------------------------------------------------------- /documentation/topics/advanced/using-multiple-repos.md: -------------------------------------------------------------------------------- 1 | # Using Multiple Repos 2 | 3 | When scaling PostgreSQL you may want to setup _read_ replicas to improve 4 | performance and availability. This can be achieved by configuring multiple 5 | repositories in your application. 6 | 7 | ## Setup Read Replicas 8 | 9 | Following the [ecto docs](https://hexdocs.pm/ecto/replicas-and-dynamic-repositories.html), change your Repo configuration: 10 | 11 | ```elixir 12 | defmodule MyApp.Repo do 13 | use Ecto.Repo, 14 | otp_app: :my_app, 15 | adapter: Ecto.Adapters.Postgres 16 | 17 | @replicas [ 18 | MyApp.Repo.Replica1, 19 | MyApp.Repo.Replica2, 20 | MyApp.Repo.Replica3, 21 | MyApp.Repo.Replica4 22 | ] 23 | 24 | def replica do 25 | Enum.random(@replicas) 26 | end 27 | 28 | for repo <- @replicas do 29 | defmodule repo do 30 | use Ecto.Repo, 31 | otp_app: :my_app, 32 | adapter: Ecto.Adapters.Postgres, 33 | read_only: true 34 | end 35 | end 36 | end 37 | ``` 38 | 39 | ## Configure AshPostgres 40 | 41 | Now change the `repo` argument for your `postgres` block as such: 42 | 43 | ```elixir 44 | defmodule MyApp.MyDomain.MyResource do 45 | use Ash.Resource, 46 | date_layer: AshPostgres.DataLayer 47 | 48 | postgres do 49 | table "my_resources" 50 | repo fn 51 | _resource, :read -> MyApp.Repo.replica() 52 | _resource, :mutate -> MyApp.Repo 53 | end 54 | end 55 | end 56 | ``` 57 | -------------------------------------------------------------------------------- /documentation/topics/development/testing.md: -------------------------------------------------------------------------------- 1 | # Testing with AshPostgres 2 | 3 | When using AshPostgres resources in tests, you will likely want to include use a test case similar to the following. This will ensure that your repo runs everything in a transaction. 4 | 5 | ```elixir 6 | defmodule MyApp.DataCase do 7 | @moduledoc """ 8 | This module defines the setup for tests requiring 9 | access to the application's data layer. 10 | 11 | You may define functions here to be used as helpers in 12 | your tests. 13 | 14 | Finally, if the test case interacts with the database, 15 | we enable the SQL sandbox, so changes done to the database 16 | are reverted at the end of every test. If you are using 17 | PostgreSQL, you can even run database tests asynchronously 18 | by setting `use AshHq.DataCase, async: true`, although 19 | this option is not recommended for other databases. 20 | """ 21 | 22 | use ExUnit.CaseTemplate 23 | 24 | using do 25 | quote do 26 | alias MyApp.Repo 27 | 28 | import Ecto 29 | import Ecto.Changeset 30 | import Ecto.Query 31 | import MyApp.DataCase 32 | end 33 | end 34 | 35 | setup tags do 36 | pid = Ecto.Adapters.SQL.Sandbox.start_owner!(MyApp.Repo, shared: not tags[:async]) 37 | on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end) 38 | :ok 39 | end 40 | end 41 | ``` 42 | 43 | This should be coupled with to make sure that Ash does not spawn any tasks. 44 | 45 | ```elixir 46 | config :ash, :disable_async?, true 47 | ``` 48 | -------------------------------------------------------------------------------- /documentation/tutorials/set-up-with-existing-database.md: -------------------------------------------------------------------------------- 1 | # Setting AshPostgres up with an existing database 2 | 3 | If you already have a postgres database and you'd like to get 4 | started quickly, you can scaffold resources directly from your 5 | database. 6 | 7 | First, create an application with AshPostgres if you haven't already: 8 | 9 | ```bash 10 | mix igniter.new my_app 11 | --install ash,ash_postgres 12 | --with phx.new # add this if you will be using phoenix too 13 | ``` 14 | 15 | Then, go into your `config/dev.exs` and configure your repo to use 16 | your existing database. 17 | 18 | Finally, run: 19 | 20 | ```bash 21 | mix ash_postgres.gen.resources MyApp.MyDomain --tables table1,table2,table3 22 | ``` 23 | 24 | ## More fine grained control 25 | 26 | You may want to do multiple passes to separate your application into multiple domains. For example: 27 | 28 | ```bash 29 | mix ash_postgres.gen.resources MyApp.Accounts --tables users,roles,tokens 30 | mix ash_postgres.gen.resources MyApp.Blog --tables posts,comments 31 | ``` 32 | 33 | See the docs for `mix ash_postgres.gen.resources` for more information. 34 | -------------------------------------------------------------------------------- /lib/check_constraint.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.CheckConstraint do 2 | @moduledoc "Represents a configured check constraint on the table backing a resource" 3 | 4 | defstruct [:attribute, :name, :message, :check] 5 | 6 | def schema do 7 | [ 8 | attribute: [ 9 | type: :any, 10 | doc: 11 | "The attribute or list of attributes to which an error will be added if the check constraint fails", 12 | required: true 13 | ], 14 | name: [ 15 | type: :string, 16 | required: true, 17 | doc: "The name of the constraint" 18 | ], 19 | message: [ 20 | type: :string, 21 | doc: "The message to be added if the check constraint fails" 22 | ], 23 | check: [ 24 | type: :string, 25 | doc: 26 | "The contents of the check. If this is set, the migration generator will include it when generating migrations" 27 | ] 28 | ] 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/custom_aggregate.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.CustomAggregate do 2 | @moduledoc """ 3 | A custom aggregate implementation for ecto. 4 | """ 5 | 6 | @doc """ 7 | The dynamic expression to create the aggregate. 8 | 9 | The binding refers to the resource being aggregated, 10 | use `as(^binding)` to reference it. 11 | 12 | For example: 13 | 14 | Ecto.Query.dynamic( 15 | [], 16 | fragment("string_agg(?, ?)", field(as(^binding), ^opts[:field]), ^opts[:delimiter]) 17 | ) 18 | """ 19 | @callback dynamic(opts :: Keyword.t(), binding :: integer) :: Ecto.Query.dynamic_expr() 20 | 21 | defmacro __using__(_) do 22 | quote do 23 | @behaviour AshPostgres.CustomAggregate 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/custom_extension.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.CustomExtension do 2 | @moduledoc """ 3 | A custom extension implementation. 4 | """ 5 | 6 | @callback install(version :: integer) :: String.t() 7 | 8 | @callback uninstall(version :: integer) :: String.t() 9 | 10 | defmacro __using__(name: name, latest_version: latest_version) do 11 | quote do 12 | @behaviour AshPostgres.CustomExtension 13 | 14 | @extension_name unquote(name) 15 | @extension_latest_version unquote(latest_version) 16 | 17 | def extension, do: {@extension_name, @extension_latest_version, &install/1, &uninstall/1} 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/functions/binding.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Functions.Binding do 2 | @moduledoc """ 3 | Refers to the current table binding. 4 | """ 5 | 6 | use Ash.Query.Function, name: :binding 7 | 8 | def args, do: [[]] 9 | end 10 | -------------------------------------------------------------------------------- /lib/functions/ilike.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Functions.ILike do 2 | @moduledoc """ 3 | Maps to the builtin postgres function `ilike`. 4 | """ 5 | 6 | use Ash.Query.Function, name: :ilike, predicate?: true 7 | 8 | def args, do: [[:string, :string]] 9 | end 10 | -------------------------------------------------------------------------------- /lib/functions/like.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Functions.Like do 2 | @moduledoc """ 3 | Maps to the builtin postgres function `like`. 4 | """ 5 | 6 | use Ash.Query.Function, name: :like, predicate?: true 7 | 8 | def args, do: [[:string, :string]] 9 | end 10 | -------------------------------------------------------------------------------- /lib/functions/trigram_similarity.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Functions.TrigramSimilarity do 2 | @moduledoc """ 3 | Maps to the builtin postgres trigram similarity function. Requires `pgtrgm` extension to be installed. 4 | 5 | See the postgres docs on [trigram](https://www.postgresql.org/docs/9.6/pgtrgm.html) for more information. 6 | 7 | Requires the pg_trgm extension. Configure which extensions you have installed in your `AshPostgres.Repo` 8 | 9 | # Example 10 | 11 | filter(query, trigram_similarity(name, "geoff") > 0.4) 12 | """ 13 | 14 | use Ash.Query.Function, name: :trigram_similarity 15 | 16 | def args, do: [[:string, :string]] 17 | end 18 | -------------------------------------------------------------------------------- /lib/functions/vector_cosine_distance.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Functions.VectorCosineDistance do 2 | @moduledoc """ 3 | Maps to the vector cosine distance operator. Requires `vector` extension to be installed. 4 | """ 5 | 6 | use Ash.Query.Function, name: :vector_cosine_distance 7 | 8 | def args, do: [[:vector, :vector]] 9 | 10 | def returns, do: [:float] 11 | end 12 | -------------------------------------------------------------------------------- /lib/functions/vector_l2_distance.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Functions.VectorL2Distance do 2 | @moduledoc """ 3 | Maps to the vector l2 distance operator. Requires `vector` extension to be installed. 4 | """ 5 | 6 | use Ash.Query.Function, name: :vector_l2_distance 7 | 8 | def args, do: [[:vector, :vector]] 9 | 10 | def returns, do: [:float] 11 | end 12 | -------------------------------------------------------------------------------- /lib/manual_relationship.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.ManualRelationship do 2 | @moduledoc "A behavior for postgres-specific manual relationship functionality" 3 | 4 | @callback ash_postgres_join( 5 | source_query :: Ecto.Query.t(), 6 | opts :: Keyword.t(), 7 | current_binding :: term, 8 | destination_binding :: term, 9 | type :: :inner | :left, 10 | destination_query :: Ecto.Query.t() 11 | ) :: {:ok, Ecto.Query.t()} | {:error, term} 12 | 13 | @callback ash_postgres_subquery( 14 | opts :: Keyword.t(), 15 | current_binding :: term, 16 | destination_binding :: term, 17 | destination_query :: Ecto.Query.t() 18 | ) :: {:ok, Ecto.Query.t()} | {:error, term} 19 | 20 | defmacro __using__(_) do 21 | quote do 22 | @behaviour AshPostgres.ManualRelationship 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/migration_compile_cache.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.MigrationCompileCache do 2 | @moduledoc """ 3 | A cache for the compiled migrations. 4 | 5 | This is used to avoid recompiling the migration files 6 | every time a migration is run, as well as ensuring that 7 | migrations are compiled sequentially. 8 | 9 | This is important because otherwise there is a race condition 10 | where two invocations could be compiling the same migration at 11 | once, which would error out. 12 | """ 13 | 14 | def start_link(opts \\ %{}) do 15 | Agent.start_link(fn -> opts end, name: __MODULE__) 16 | end 17 | 18 | @doc """ 19 | Compile a file, caching the result for future calls. 20 | """ 21 | def compile_file(file) do 22 | Agent.get_and_update(__MODULE__, fn state -> 23 | new_state = ensure_compiled(state, file) 24 | {Map.get(new_state, file), new_state} 25 | end) 26 | end 27 | 28 | defp ensure_compiled(state, file) do 29 | case Map.get(state, file) do 30 | nil -> 31 | Code.put_compiler_option(:ignore_module_conflict, true) 32 | compiled = Code.compile_file(file) 33 | Map.put(state, file, compiled) 34 | 35 | _ -> 36 | state 37 | end 38 | after 39 | Code.put_compiler_option(:ignore_module_conflict, false) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/mix/tasks/ash_postgres.create.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.AshPostgres.Create do 2 | use Mix.Task 3 | 4 | @shortdoc "Creates the repository storage" 5 | 6 | @switches [ 7 | quiet: :boolean, 8 | domains: :string, 9 | no_compile: :boolean, 10 | no_deps_check: :boolean, 11 | repo: :string, 12 | r: :string 13 | ] 14 | 15 | @aliases [ 16 | q: :quiet, 17 | r: :repo 18 | ] 19 | 20 | @moduledoc """ 21 | Create the storage for repos in all resources for the given (or configured) domains. 22 | 23 | ## Examples 24 | 25 | mix ash_postgres.create 26 | mix ash_postgres.create --domains MyApp.Domain1,MyApp.Domain2 27 | 28 | ## Command line options 29 | 30 | * `--domains` - the domains who's repos you want to migrate. 31 | * `-r, --repo` - the repo to create 32 | * `--quiet` - do not log output 33 | * `--no-compile` - do not compile before creating 34 | * `--no-deps-check` - do not compile before creating 35 | """ 36 | 37 | @doc false 38 | def run(args) do 39 | {opts, _} = OptionParser.parse!(args, strict: @switches, aliases: @aliases) 40 | 41 | repos = 42 | AshPostgres.Mix.Helpers.repos!(opts, args) 43 | |> Enum.filter(fn repo -> repo.create?() end) 44 | 45 | repo_args = 46 | Enum.flat_map(repos, fn repo -> 47 | ["-r", to_string(repo)] 48 | end) 49 | 50 | rest_opts = AshPostgres.Mix.Helpers.delete_arg(args, "--domains") 51 | 52 | Mix.Task.reenable("ecto.create") 53 | Mix.Task.run("ecto.create", repo_args ++ rest_opts) 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/repo/before_compile.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Repo.BeforeCompile do 2 | @moduledoc false 3 | 4 | defmacro __before_compile__(_env) do 5 | quote do 6 | if !Module.defines?(__MODULE__, {:min_pg_version, 0}, :def) do 7 | IO.warn(""" 8 | Please define `min_pg_version/0` in repo module: #{inspect(__MODULE__)} 9 | 10 | For example: 11 | 12 | def min_pg_version do 13 | %Version{major: 16, minor: 0, patch: 0} 14 | end 15 | 16 | The lowest compatible version is being assumed. 17 | """) 18 | 19 | def min_pg_version do 20 | %Version{major: 13, minor: 0, patch: 0} 21 | end 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/statement.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Statement do 2 | @moduledoc "Represents a custom statement to be run in generated migrations" 3 | 4 | @fields [ 5 | :name, 6 | :up, 7 | :down, 8 | :code? 9 | ] 10 | 11 | defstruct @fields 12 | 13 | def fields, do: @fields 14 | 15 | @schema [ 16 | name: [ 17 | type: :atom, 18 | required: true, 19 | doc: """ 20 | The name of the statement, must be unique within the resource 21 | """ 22 | ], 23 | code?: [ 24 | type: :boolean, 25 | default: false, 26 | doc: """ 27 | By default, we place the strings inside of ecto migration's `execute/1` function and assume they are sql. Use this option if you want to provide custom elixir code to be placed directly in the migrations 28 | """ 29 | ], 30 | up: [ 31 | type: :string, 32 | doc: """ 33 | How to create the structure of the statement 34 | """, 35 | required: true 36 | ], 37 | down: [ 38 | type: :string, 39 | doc: "How to tear down the structure of the statement", 40 | required: true 41 | ] 42 | ] 43 | 44 | def schema, do: @schema 45 | end 46 | -------------------------------------------------------------------------------- /lib/type.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Type do 2 | @moduledoc """ 3 | Postgres specific callbacks for `Ash.Type`. 4 | 5 | Use this in addition to `Ash.Type`. 6 | """ 7 | 8 | @callback value_to_postgres_default(Ash.Type.t(), Ash.Type.constraints(), term) :: 9 | {:ok, String.t()} | :error 10 | 11 | @callback postgres_reference_expr(Ash.Type.t(), Ash.Type.constraints(), term) :: 12 | {:ok, term} | :error 13 | 14 | @callback migration_type(Ash.Type.constraints()) :: term() 15 | 16 | @optional_callbacks value_to_postgres_default: 3, 17 | postgres_reference_expr: 3, 18 | migration_type: 1 19 | 20 | defmacro __using__(_) do 21 | quote do 22 | @behaviour AshPostgres.Type 23 | 24 | def value_to_postgres_default(_, _, _), do: :error 25 | def postgres_reference_expr(_, _, _), do: :error 26 | 27 | defoverridable value_to_postgres_default: 3, 28 | postgres_reference_expr: 3 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/types/ci_string_wrapper.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Type.CiStringWrapper do 2 | @moduledoc false 3 | use Ash.Type 4 | 5 | @impl true 6 | def storage_type(_), do: :citext 7 | 8 | @impl true 9 | defdelegate cast_input(value, constraints), to: Ash.Type.CiString 10 | @impl true 11 | defdelegate cast_stored(value, constraints), to: Ash.Type.CiString 12 | @impl true 13 | defdelegate dump_to_native(value, constraints), to: Ash.Type.CiString 14 | end 15 | -------------------------------------------------------------------------------- /lib/types/string_wrapper.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Type.StringWrapper do 2 | @moduledoc false 3 | use Ash.Type 4 | 5 | @impl true 6 | def storage_type(_), do: :text 7 | 8 | @impl true 9 | defdelegate cast_input(value, constraints), to: Ash.Type.String 10 | @impl true 11 | defdelegate cast_stored(value, constraints), to: Ash.Type.String 12 | @impl true 13 | defdelegate dump_to_native(value, constraints), to: Ash.Type.String 14 | end 15 | -------------------------------------------------------------------------------- /lib/types/timestamptz_usec.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TimestamptzUsec do 2 | @moduledoc """ 3 | Implements the PostgresSQL [timestamptz](https://www.postgresql.org/docs/current/datatype-datetime.html) (aka `timestamp with time zone`) type with nanosecond precision. 4 | 5 | ```elixir 6 | attribute :timestamp, AshPostgres.TimestamptzUsec 7 | timestamps type: AshPostgres.TimestamptzUsec 8 | ``` 9 | 10 | Alternatively, you can set up a shortname: 11 | 12 | ```elixir 13 | # config.exs 14 | config :ash, :custom_types, timestamptz_usec: AshPostgres.TimestamptzUsec 15 | ``` 16 | 17 | After saving, you will need to run `mix compile ash --force`. 18 | 19 | ```elixir 20 | attribute :timestamp, :timestamptz_usec 21 | timestamps type: :timestamptz_usec 22 | ``` 23 | 24 | Please see `AshPostgres.Timestamptz` for details about the usecase for this type. 25 | """ 26 | use Ash.Type.NewType, subtype_of: :datetime, constraints: [precision: :microsecond] 27 | 28 | @impl true 29 | def storage_type(_constraints) do 30 | :"timestamptz(6)" 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/types/tsquery.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Tsquery do 2 | @moduledoc """ 3 | A thin wrapper around `:string` for working with tsquery types in calculations. 4 | 5 | A calculation of this type cannot be selected, but may be used in calculations. 6 | """ 7 | 8 | use Ash.Type.NewType, subtype_of: :term 9 | 10 | @impl true 11 | def storage_type(_), do: :tsquery 12 | end 13 | -------------------------------------------------------------------------------- /lib/types/tsvector.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Tsvector do 2 | @moduledoc """ 3 | A type for representing postgres' tsvectors. 4 | 5 | Values will be a list of `Postgrex.Lexeme` 6 | """ 7 | 8 | use Ash.Type.NewType, subtype_of: :term 9 | 10 | @impl true 11 | def storage_type(_), do: :tsvector 12 | 13 | @impl true 14 | def cast_input(nil, _) do 15 | {:ok, nil} 16 | end 17 | 18 | def cast_input(values, _) when is_list(values) do 19 | if Enum.all?(values, &is_struct(&1, Postgrex.Lexeme)) do 20 | {:ok, values} 21 | else 22 | :error 23 | end 24 | end 25 | 26 | def cast_input(_, _) do 27 | :error 28 | end 29 | 30 | @impl true 31 | def dump_to_native(nil, _) do 32 | {:ok, nil} 33 | end 34 | 35 | def dump_to_native(values, _) when is_list(values) do 36 | if Enum.all?(values, &is_struct(&1, Postgrex.Lexeme)) do 37 | {:ok, values} 38 | else 39 | :error 40 | end 41 | end 42 | 43 | def dump_to_native(_, _) do 44 | :error 45 | end 46 | 47 | @impl true 48 | def cast_stored(nil, _) do 49 | {:ok, nil} 50 | end 51 | 52 | def cast_stored(values, _) when is_list(values) do 53 | if Enum.all?(values, &is_struct(&1, Postgrex.Lexeme)) do 54 | {:ok, values} 55 | else 56 | :error 57 | end 58 | end 59 | 60 | def cast_stored(_, _) do 61 | :error 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/verifiers/ensure_table_or_polymorphic.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Verifiers.EnsureTableOrPolymorphic do 2 | @moduledoc false 3 | use Spark.Dsl.Verifier 4 | alias Spark.Dsl.Verifier 5 | 6 | def verify(dsl) do 7 | if Verifier.get_option(dsl, [:postgres], :polymorphic?) || 8 | Verifier.get_option(dsl, [:postgres], :table) do 9 | :ok 10 | else 11 | resource = Verifier.get_persisted(dsl, :module) 12 | 13 | raise Spark.Error.DslError, 14 | module: resource, 15 | message: """ 16 | Must configure a table for #{inspect(resource)}. 17 | 18 | For example: 19 | 20 | ```elixir 21 | postgres do 22 | table "the_table" 23 | repo YourApp.Repo 24 | end 25 | ``` 26 | """, 27 | path: [:postgres, :table] 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/verifiers/prevent_multidimensional_array_aggregates.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Verifiers.PreventMultidimensionalArrayAggregates do 2 | @moduledoc false 3 | use Spark.Dsl.Verifier 4 | alias Spark.Dsl.Verifier 5 | 6 | def verify(dsl) do 7 | resource = Verifier.get_persisted(dsl, :module) 8 | 9 | dsl 10 | |> Ash.Resource.Info.aggregates() 11 | |> Stream.filter(&(&1.kind in [:list, :first])) 12 | |> Stream.filter(& &1.field) 13 | |> Enum.each(fn aggregate -> 14 | related = Ash.Resource.Info.related(resource, aggregate.relationship_path) 15 | 16 | related_field = 17 | if related do 18 | Ash.Resource.Info.field(related, aggregate.field) 19 | end 20 | 21 | type = 22 | if related_field do 23 | related_field.type 24 | end 25 | 26 | case type do 27 | {:array, _} -> 28 | raise Spark.Error.DslError, 29 | module: resource, 30 | path: [:aggregates, aggregate.name], 31 | message: """ 32 | Aggregate not supported. 33 | 34 | Aggregate #{inspect(resource)}.#{aggregate.name} is not supported, because its type is `#{aggregate.kind}`, and the destination attribute is an array. 35 | 36 | Postgres does not support multidimensional arrays with differing lengths internally. In the future we may be able to remove this restriction 37 | for the `:first` type aggregate, but likely never for `:list`. In the meantime, you will have to use a custom calculation to get this data. 38 | """ 39 | 40 | _ -> 41 | :ok 42 | end 43 | end) 44 | 45 | :ok 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/verifiers/validate_references.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Verifiers.ValidateReferences do 2 | @moduledoc false 3 | use Spark.Dsl.Verifier 4 | alias Spark.Dsl.Verifier 5 | 6 | def verify(dsl) do 7 | dsl 8 | |> AshPostgres.DataLayer.Info.references() 9 | |> Enum.each(fn reference -> 10 | if !Ash.Resource.Info.relationship(dsl, reference.relationship) do 11 | raise Spark.Error.DslError, 12 | path: [:postgres, :references, reference.relationship], 13 | module: Verifier.get_persisted(dsl, :module), 14 | message: 15 | "Found reference configuration for relationship `#{reference.relationship}`, but no such relationship exists" 16 | end 17 | end) 18 | 19 | :ok 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/version_agent.ex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ash-project/ash_postgres/5b8f46ffb52378a818dee7278d9ab9702dc12121/lib/version_agent.ex -------------------------------------------------------------------------------- /logos/small-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ash-project/ash_postgres/5b8f46ffb52378a818dee7278d9ab9702dc12121/logos/small-logo.png -------------------------------------------------------------------------------- /priv/dev_test_repo/migrations/20250526214827_migrate_resources1.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.DevTestRepo.Migrations.MigrateResources1 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | create table(:multitenant_orgs, primary_key: false) do 12 | add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) 13 | add(:name, :text) 14 | add(:owner_id, :uuid) 15 | end 16 | 17 | create( 18 | unique_index(:multitenant_orgs, [:id, :name], name: "multitenant_orgs_unique_by_name_index") 19 | ) 20 | end 21 | 22 | def down do 23 | drop_if_exists( 24 | unique_index(:multitenant_orgs, [:id, :name], name: "multitenant_orgs_unique_by_name_index") 25 | ) 26 | 27 | drop(table(:multitenant_orgs)) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /priv/resource_snapshots/dev_test_repo/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "ash_functions_version": 5, 3 | "installed": [ 4 | "ash-functions", 5 | "uuid-ossp", 6 | "pg_trgm", 7 | "citext", 8 | "demo-functions_v1", 9 | "ltree" 10 | ] 11 | } -------------------------------------------------------------------------------- /priv/resource_snapshots/test_no_sandbox_repo/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "ash_functions_version": 5, 3 | "installed": [ 4 | "ash-functions", 5 | "uuid-ossp", 6 | "pg_trgm", 7 | "citext", 8 | "demo-functions_v1" 9 | ] 10 | } -------------------------------------------------------------------------------- /priv/resource_snapshots/test_repo/authors/20220805191443.json: -------------------------------------------------------------------------------- 1 | { 2 | "attributes": [ 3 | { 4 | "allow_nil?": false, 5 | "default": "fragment(\"uuid_generate_v4()\")", 6 | "generated?": false, 7 | "primary_key?": true, 8 | "references": null, 9 | "size": null, 10 | "source": "id", 11 | "type": "uuid" 12 | }, 13 | { 14 | "allow_nil?": true, 15 | "default": "nil", 16 | "generated?": false, 17 | "primary_key?": false, 18 | "references": null, 19 | "size": null, 20 | "source": "first_name", 21 | "type": "text" 22 | }, 23 | { 24 | "allow_nil?": true, 25 | "default": "nil", 26 | "generated?": false, 27 | "primary_key?": false, 28 | "references": null, 29 | "size": null, 30 | "source": "last_name", 31 | "type": "text" 32 | }, 33 | { 34 | "allow_nil?": true, 35 | "default": "nil", 36 | "generated?": false, 37 | "primary_key?": false, 38 | "references": null, 39 | "size": null, 40 | "source": "bio", 41 | "type": "map" 42 | } 43 | ], 44 | "base_filter": null, 45 | "check_constraints": [], 46 | "custom_indexes": [], 47 | "custom_statements": [], 48 | "has_create_action": true, 49 | "hash": "FD1ACF51CCB925CEE62081C1F1A66091E440ABB26C010F192BED33C2E27055CB", 50 | "identities": [], 51 | "multitenancy": { 52 | "attribute": null, 53 | "global": null, 54 | "strategy": null 55 | }, 56 | "repo": "Elixir.AshPostgres.TestRepo", 57 | "schema": null, 58 | "table": "authors" 59 | } -------------------------------------------------------------------------------- /priv/resource_snapshots/test_repo/complex_calculations_certifications/20230816231942.json: -------------------------------------------------------------------------------- 1 | { 2 | "attributes": [ 3 | { 4 | "default": "fragment(\"uuid_generate_v4()\")", 5 | "size": null, 6 | "type": "uuid", 7 | "source": "id", 8 | "references": null, 9 | "allow_nil?": false, 10 | "primary_key?": true, 11 | "generated?": false 12 | } 13 | ], 14 | "table": "complex_calculations_certifications", 15 | "hash": "A4D8129693BDC95C72E91842B17BB1B44951D91A87DC166A3371D052E4D27C1F", 16 | "repo": "Elixir.AshPostgres.TestRepo", 17 | "schema": null, 18 | "check_constraints": [], 19 | "identities": [], 20 | "custom_indexes": [], 21 | "multitenancy": { 22 | "global": null, 23 | "attribute": null, 24 | "strategy": null 25 | }, 26 | "base_filter": null, 27 | "custom_statements": [], 28 | "has_create_action": true 29 | } -------------------------------------------------------------------------------- /priv/resource_snapshots/test_repo/complex_calculations_certifications/20240327211150.json: -------------------------------------------------------------------------------- 1 | { 2 | "attributes": [ 3 | { 4 | "default": "fragment(\"gen_random_uuid()\")", 5 | "size": null, 6 | "type": "uuid", 7 | "source": "id", 8 | "references": null, 9 | "primary_key?": true, 10 | "allow_nil?": false, 11 | "generated?": false 12 | } 13 | ], 14 | "table": "complex_calculations_certifications", 15 | "hash": "EAD1F09DA125BB08FDB1186B925BCD84F0DAD8B7C996318E30B3904A7F96EE8D", 16 | "repo": "Elixir.AshPostgres.TestRepo", 17 | "multitenancy": { 18 | "global": null, 19 | "attribute": null, 20 | "strategy": null 21 | }, 22 | "schema": null, 23 | "identities": [], 24 | "has_create_action": true, 25 | "custom_indexes": [], 26 | "custom_statements": [], 27 | "base_filter": null, 28 | "check_constraints": [] 29 | } -------------------------------------------------------------------------------- /priv/resource_snapshots/test_repo/complex_calculations_channels/20231116013020.json: -------------------------------------------------------------------------------- 1 | { 2 | "attributes": [ 3 | { 4 | "default": "fragment(\"uuid_generate_v4()\")", 5 | "size": null, 6 | "type": "uuid", 7 | "source": "id", 8 | "references": null, 9 | "primary_key?": true, 10 | "allow_nil?": false, 11 | "generated?": false 12 | }, 13 | { 14 | "default": "fragment(\"now()\")", 15 | "size": null, 16 | "type": "utc_datetime_usec", 17 | "source": "created_at", 18 | "references": null, 19 | "primary_key?": false, 20 | "allow_nil?": false, 21 | "generated?": false 22 | }, 23 | { 24 | "default": "fragment(\"now()\")", 25 | "size": null, 26 | "type": "utc_datetime_usec", 27 | "source": "updated_at", 28 | "references": null, 29 | "primary_key?": false, 30 | "allow_nil?": false, 31 | "generated?": false 32 | } 33 | ], 34 | "table": "complex_calculations_channels", 35 | "hash": "2C35FB16B98FA229F91F69D2D3BEEDA41BAB55896E59247A96D9068AD5BF000A", 36 | "repo": "Elixir.AshPostgres.TestRepo", 37 | "schema": null, 38 | "check_constraints": [], 39 | "identities": [], 40 | "custom_indexes": [], 41 | "multitenancy": { 42 | "global": null, 43 | "attribute": null, 44 | "strategy": null 45 | }, 46 | "base_filter": null, 47 | "custom_statements": [], 48 | "has_create_action": true 49 | } -------------------------------------------------------------------------------- /priv/resource_snapshots/test_repo/complex_calculations_channels/20240327211150.json: -------------------------------------------------------------------------------- 1 | { 2 | "attributes": [ 3 | { 4 | "default": "fragment(\"gen_random_uuid()\")", 5 | "size": null, 6 | "type": "uuid", 7 | "source": "id", 8 | "references": null, 9 | "primary_key?": true, 10 | "allow_nil?": false, 11 | "generated?": false 12 | }, 13 | { 14 | "default": "fragment(\"now()\")", 15 | "size": null, 16 | "type": "utc_datetime_usec", 17 | "source": "created_at", 18 | "references": null, 19 | "primary_key?": false, 20 | "allow_nil?": false, 21 | "generated?": false 22 | }, 23 | { 24 | "default": "fragment(\"now()\")", 25 | "size": null, 26 | "type": "utc_datetime_usec", 27 | "source": "updated_at", 28 | "references": null, 29 | "primary_key?": false, 30 | "allow_nil?": false, 31 | "generated?": false 32 | } 33 | ], 34 | "table": "complex_calculations_channels", 35 | "hash": "3BEBD2B361ED4CA69120920B131113996BF5530E24952310AB88E49A79906943", 36 | "repo": "Elixir.AshPostgres.TestRepo", 37 | "multitenancy": { 38 | "global": null, 39 | "attribute": null, 40 | "strategy": null 41 | }, 42 | "schema": null, 43 | "identities": [], 44 | "has_create_action": true, 45 | "custom_indexes": [], 46 | "custom_statements": [], 47 | "base_filter": null, 48 | "check_constraints": [] 49 | } -------------------------------------------------------------------------------- /priv/resource_snapshots/test_repo/complex_calculations_channels/20240327211917.json: -------------------------------------------------------------------------------- 1 | { 2 | "attributes": [ 3 | { 4 | "default": "fragment(\"gen_random_uuid()\")", 5 | "size": null, 6 | "type": "uuid", 7 | "source": "id", 8 | "references": null, 9 | "primary_key?": true, 10 | "allow_nil?": false, 11 | "generated?": false 12 | }, 13 | { 14 | "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", 15 | "size": null, 16 | "type": "utc_datetime_usec", 17 | "source": "created_at", 18 | "references": null, 19 | "primary_key?": false, 20 | "allow_nil?": false, 21 | "generated?": false 22 | }, 23 | { 24 | "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", 25 | "size": null, 26 | "type": "utc_datetime_usec", 27 | "source": "updated_at", 28 | "references": null, 29 | "primary_key?": false, 30 | "allow_nil?": false, 31 | "generated?": false 32 | } 33 | ], 34 | "table": "complex_calculations_channels", 35 | "hash": "660EEED8929709A3B61F1C8C6804305240DE60664B7BE05639869C7E862AFE6F", 36 | "repo": "Elixir.AshPostgres.TestRepo", 37 | "multitenancy": { 38 | "global": null, 39 | "attribute": null, 40 | "strategy": null 41 | }, 42 | "schema": null, 43 | "identities": [], 44 | "has_create_action": true, 45 | "custom_indexes": [], 46 | "custom_statements": [], 47 | "base_filter": null, 48 | "check_constraints": [] 49 | } -------------------------------------------------------------------------------- /priv/resource_snapshots/test_repo/csv/20250320225052.json: -------------------------------------------------------------------------------- 1 | { 2 | "attributes": [ 3 | { 4 | "allow_nil?": false, 5 | "default": "fragment(\"gen_random_uuid()\")", 6 | "generated?": false, 7 | "primary_key?": true, 8 | "references": null, 9 | "size": null, 10 | "source": "id", 11 | "type": "uuid" 12 | }, 13 | { 14 | "allow_nil?": true, 15 | "default": "nil", 16 | "generated?": false, 17 | "primary_key?": false, 18 | "references": null, 19 | "size": null, 20 | "source": "column_mapping_embedded", 21 | "type": [ 22 | "array", 23 | "map" 24 | ] 25 | }, 26 | { 27 | "allow_nil?": true, 28 | "default": "nil", 29 | "generated?": false, 30 | "primary_key?": false, 31 | "references": null, 32 | "size": null, 33 | "source": "column_mapping_new_type", 34 | "type": [ 35 | "array", 36 | "map" 37 | ] 38 | } 39 | ], 40 | "base_filter": null, 41 | "check_constraints": [], 42 | "custom_indexes": [], 43 | "custom_statements": [], 44 | "has_create_action": true, 45 | "hash": "B336E8AA99A8DCB5D4FB2965D41E33F6E4F172A10CE24F5DA5E4A5912DD5C87E", 46 | "identities": [], 47 | "multitenancy": { 48 | "attribute": null, 49 | "global": null, 50 | "strategy": null 51 | }, 52 | "repo": "Elixir.AshPostgres.TestRepo", 53 | "schema": null, 54 | "table": "csv" 55 | } -------------------------------------------------------------------------------- /priv/resource_snapshots/test_repo/entities/20240109160153.json: -------------------------------------------------------------------------------- 1 | { 2 | "attributes": [ 3 | { 4 | "default": "fragment(\"uuid_generate_v4()\")", 5 | "size": null, 6 | "type": "uuid", 7 | "source": "id", 8 | "references": null, 9 | "allow_nil?": false, 10 | "generated?": false, 11 | "primary_key?": true 12 | }, 13 | { 14 | "default": "nil", 15 | "size": null, 16 | "type": "text", 17 | "source": "full_name", 18 | "references": null, 19 | "allow_nil?": false, 20 | "generated?": false, 21 | "primary_key?": false 22 | }, 23 | { 24 | "default": "fragment(\"now()\")", 25 | "size": null, 26 | "type": "utc_datetime_usec", 27 | "source": "inserted_at", 28 | "references": null, 29 | "allow_nil?": false, 30 | "generated?": false, 31 | "primary_key?": false 32 | }, 33 | { 34 | "default": "fragment(\"now()\")", 35 | "size": null, 36 | "type": "utc_datetime_usec", 37 | "source": "updated_at", 38 | "references": null, 39 | "allow_nil?": false, 40 | "generated?": false, 41 | "primary_key?": false 42 | } 43 | ], 44 | "table": "entities", 45 | "hash": "B3F53BBC4C888000775A43EBF4B52EA9CFCF37C82A9F1FF50BBDEDB78015DC54", 46 | "repo": "Elixir.AshPostgres.TestRepo", 47 | "identities": [], 48 | "schema": null, 49 | "check_constraints": [], 50 | "custom_indexes": [], 51 | "base_filter": null, 52 | "multitenancy": { 53 | "global": null, 54 | "attribute": null, 55 | "strategy": null 56 | }, 57 | "custom_statements": [], 58 | "has_create_action": false 59 | } -------------------------------------------------------------------------------- /priv/resource_snapshots/test_repo/entities/20240327211150.json: -------------------------------------------------------------------------------- 1 | { 2 | "attributes": [ 3 | { 4 | "default": "fragment(\"gen_random_uuid()\")", 5 | "size": null, 6 | "type": "uuid", 7 | "source": "id", 8 | "references": null, 9 | "primary_key?": true, 10 | "allow_nil?": false, 11 | "generated?": false 12 | }, 13 | { 14 | "default": "nil", 15 | "size": null, 16 | "type": "text", 17 | "source": "full_name", 18 | "references": null, 19 | "primary_key?": false, 20 | "allow_nil?": false, 21 | "generated?": false 22 | }, 23 | { 24 | "default": "fragment(\"now()\")", 25 | "size": null, 26 | "type": "utc_datetime_usec", 27 | "source": "inserted_at", 28 | "references": null, 29 | "primary_key?": false, 30 | "allow_nil?": false, 31 | "generated?": false 32 | }, 33 | { 34 | "default": "fragment(\"now()\")", 35 | "size": null, 36 | "type": "utc_datetime_usec", 37 | "source": "updated_at", 38 | "references": null, 39 | "primary_key?": false, 40 | "allow_nil?": false, 41 | "generated?": false 42 | } 43 | ], 44 | "table": "entities", 45 | "hash": "C996CEB931DB10DD0F1FA9FD70A309242B8A8CBC689E39901497AE8B17261DE7", 46 | "repo": "Elixir.AshPostgres.TestRepo", 47 | "multitenancy": { 48 | "global": null, 49 | "attribute": null, 50 | "strategy": null 51 | }, 52 | "schema": null, 53 | "identities": [], 54 | "has_create_action": true, 55 | "custom_indexes": [], 56 | "custom_statements": [], 57 | "base_filter": null, 58 | "check_constraints": [] 59 | } -------------------------------------------------------------------------------- /priv/resource_snapshots/test_repo/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "ash_functions_version": 5, 3 | "installed": [ 4 | "ash-functions", 5 | "uuid-ossp", 6 | "pg_trgm", 7 | "citext", 8 | "demo-functions_v1", 9 | "ltree" 10 | ] 11 | } -------------------------------------------------------------------------------- /priv/resource_snapshots/test_repo/integer_posts/20220805191443.json: -------------------------------------------------------------------------------- 1 | { 2 | "attributes": [ 3 | { 4 | "allow_nil?": false, 5 | "default": "nil", 6 | "generated?": true, 7 | "primary_key?": true, 8 | "references": null, 9 | "size": null, 10 | "source": "id", 11 | "type": "bigint" 12 | }, 13 | { 14 | "allow_nil?": true, 15 | "default": "nil", 16 | "generated?": false, 17 | "primary_key?": false, 18 | "references": null, 19 | "size": null, 20 | "source": "title", 21 | "type": "text" 22 | } 23 | ], 24 | "base_filter": null, 25 | "check_constraints": [], 26 | "custom_indexes": [], 27 | "custom_statements": [], 28 | "has_create_action": true, 29 | "hash": "E80C3698D988583B16B138E9284007879243E51F9551BD356D1D77648CF8D45A", 30 | "identities": [], 31 | "multitenancy": { 32 | "attribute": null, 33 | "global": null, 34 | "strategy": null 35 | }, 36 | "repo": "Elixir.AshPostgres.TestRepo", 37 | "schema": null, 38 | "table": "integer_posts" 39 | } -------------------------------------------------------------------------------- /priv/resource_snapshots/test_repo/items/20240713134055.json: -------------------------------------------------------------------------------- 1 | { 2 | "attributes": [ 3 | { 4 | "allow_nil?": false, 5 | "default": "fragment(\"uuid_generate_v7()\")", 6 | "generated?": false, 7 | "primary_key?": true, 8 | "references": null, 9 | "size": null, 10 | "source": "id", 11 | "type": "uuid" 12 | }, 13 | { 14 | "allow_nil?": false, 15 | "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", 16 | "generated?": false, 17 | "primary_key?": false, 18 | "references": null, 19 | "size": null, 20 | "source": "inserted_at", 21 | "type": "utc_datetime_usec" 22 | }, 23 | { 24 | "allow_nil?": false, 25 | "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", 26 | "generated?": false, 27 | "primary_key?": false, 28 | "references": null, 29 | "size": null, 30 | "source": "updated_at", 31 | "type": "utc_datetime_usec" 32 | } 33 | ], 34 | "base_filter": null, 35 | "check_constraints": [], 36 | "custom_indexes": [], 37 | "custom_statements": [], 38 | "has_create_action": true, 39 | "hash": "ABF32E9B15960350C650AA0F324442FEE568688B2176BF9441D168A20241CB44", 40 | "identities": [], 41 | "multitenancy": { 42 | "attribute": null, 43 | "global": null, 44 | "strategy": null 45 | }, 46 | "repo": "Elixir.AshPostgres.TestRepo", 47 | "schema": null, 48 | "table": "items" 49 | } -------------------------------------------------------------------------------- /priv/resource_snapshots/test_repo/items/20240717153736.json: -------------------------------------------------------------------------------- 1 | { 2 | "attributes": [ 3 | { 4 | "allow_nil?": false, 5 | "default": "fragment(\"uuid_generate_v7()\")", 6 | "generated?": false, 7 | "primary_key?": true, 8 | "references": null, 9 | "size": null, 10 | "source": "id", 11 | "type": "uuid" 12 | }, 13 | { 14 | "allow_nil?": true, 15 | "default": "nil", 16 | "generated?": false, 17 | "primary_key?": false, 18 | "references": null, 19 | "size": null, 20 | "source": "key", 21 | "type": "text" 22 | }, 23 | { 24 | "allow_nil?": false, 25 | "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", 26 | "generated?": false, 27 | "primary_key?": false, 28 | "references": null, 29 | "size": null, 30 | "source": "inserted_at", 31 | "type": "utc_datetime_usec" 32 | }, 33 | { 34 | "allow_nil?": false, 35 | "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", 36 | "generated?": false, 37 | "primary_key?": false, 38 | "references": null, 39 | "size": null, 40 | "source": "updated_at", 41 | "type": "utc_datetime_usec" 42 | } 43 | ], 44 | "base_filter": null, 45 | "check_constraints": [], 46 | "custom_indexes": [], 47 | "custom_statements": [], 48 | "has_create_action": true, 49 | "hash": "571E95D3DAC0A3C7446601D96857C70384A20159928EE335D3A421FA61555158", 50 | "identities": [], 51 | "multitenancy": { 52 | "attribute": null, 53 | "global": null, 54 | "strategy": null 55 | }, 56 | "repo": "Elixir.AshPostgres.TestRepo", 57 | "schema": null, 58 | "table": "items" 59 | } -------------------------------------------------------------------------------- /priv/resource_snapshots/test_repo/multitenant_named_orgs/20250519103535.json: -------------------------------------------------------------------------------- 1 | { 2 | "attributes": [ 3 | { 4 | "allow_nil?": false, 5 | "default": "nil", 6 | "generated?": false, 7 | "primary_key?": true, 8 | "references": null, 9 | "size": null, 10 | "source": "name", 11 | "type": "text" 12 | } 13 | ], 14 | "base_filter": null, 15 | "check_constraints": [], 16 | "custom_indexes": [], 17 | "custom_statements": [], 18 | "has_create_action": true, 19 | "hash": "63124167427BA3C61197814348217EFC967CDAA398102552836E26BD93E198C8", 20 | "identities": [], 21 | "multitenancy": { 22 | "attribute": null, 23 | "global": null, 24 | "strategy": null 25 | }, 26 | "repo": "Elixir.AshPostgres.TestRepo", 27 | "schema": null, 28 | "table": "multitenant_named_orgs" 29 | } -------------------------------------------------------------------------------- /priv/resource_snapshots/test_repo/multitenant_orgs/20220805191443.json: -------------------------------------------------------------------------------- 1 | { 2 | "attributes": [ 3 | { 4 | "allow_nil?": false, 5 | "default": "fragment(\"uuid_generate_v4()\")", 6 | "generated?": false, 7 | "primary_key?": true, 8 | "references": null, 9 | "size": null, 10 | "source": "id", 11 | "type": "uuid" 12 | }, 13 | { 14 | "allow_nil?": true, 15 | "default": "nil", 16 | "generated?": false, 17 | "primary_key?": false, 18 | "references": null, 19 | "size": null, 20 | "source": "name", 21 | "type": "text" 22 | } 23 | ], 24 | "base_filter": null, 25 | "check_constraints": [], 26 | "custom_indexes": [], 27 | "custom_statements": [], 28 | "has_create_action": true, 29 | "hash": "EA53A9A03B960621ED09D98D442136FA2E6FC37C7E67E5C35A451B3440A09EE7", 30 | "identities": [ 31 | { 32 | "base_filter": null, 33 | "index_name": "multitenant_orgs_unique_by_name_index", 34 | "keys": [ 35 | "name" 36 | ], 37 | "name": "unique_by_name" 38 | } 39 | ], 40 | "multitenancy": { 41 | "attribute": "id", 42 | "global": true, 43 | "strategy": "attribute" 44 | }, 45 | "repo": "Elixir.AshPostgres.TestRepo", 46 | "schema": null, 47 | "table": "multitenant_orgs" 48 | } -------------------------------------------------------------------------------- /priv/resource_snapshots/test_repo/multitenant_orgs/20240327211150.json: -------------------------------------------------------------------------------- 1 | { 2 | "attributes": [ 3 | { 4 | "default": "fragment(\"gen_random_uuid()\")", 5 | "size": null, 6 | "type": "uuid", 7 | "source": "id", 8 | "references": null, 9 | "primary_key?": true, 10 | "allow_nil?": false, 11 | "generated?": false 12 | }, 13 | { 14 | "default": "nil", 15 | "size": null, 16 | "type": "text", 17 | "source": "name", 18 | "references": null, 19 | "primary_key?": false, 20 | "allow_nil?": true, 21 | "generated?": false 22 | } 23 | ], 24 | "table": "multitenant_orgs", 25 | "hash": "8272A81AAF88BCC843DCB78558ECE612D31CD6E3FFB0AEA17A94B0223A4815BB", 26 | "repo": "Elixir.AshPostgres.TestRepo", 27 | "multitenancy": { 28 | "global": true, 29 | "attribute": "id", 30 | "strategy": "attribute" 31 | }, 32 | "schema": null, 33 | "identities": [ 34 | { 35 | "name": "unique_by_name", 36 | "keys": [ 37 | "name" 38 | ], 39 | "base_filter": null, 40 | "all_tenants?": false, 41 | "index_name": "multitenant_orgs_unique_by_name_index" 42 | } 43 | ], 44 | "has_create_action": true, 45 | "custom_indexes": [], 46 | "custom_statements": [], 47 | "base_filter": null, 48 | "check_constraints": [] 49 | } -------------------------------------------------------------------------------- /priv/resource_snapshots/test_repo/orgs/20230129050950.json: -------------------------------------------------------------------------------- 1 | { 2 | "attributes": [ 3 | { 4 | "allow_nil?": false, 5 | "default": "fragment(\"uuid_generate_v4()\")", 6 | "generated?": false, 7 | "primary_key?": true, 8 | "references": null, 9 | "size": null, 10 | "source": "id", 11 | "type": "uuid" 12 | }, 13 | { 14 | "allow_nil?": true, 15 | "default": "nil", 16 | "generated?": false, 17 | "primary_key?": false, 18 | "references": null, 19 | "size": null, 20 | "source": "name", 21 | "type": "text" 22 | } 23 | ], 24 | "base_filter": null, 25 | "check_constraints": [], 26 | "custom_indexes": [], 27 | "custom_statements": [], 28 | "has_create_action": true, 29 | "hash": "B14556A2079B06D3ED1BF1D557B7FD1DA2D859BBB25B702352DD4D28680580D7", 30 | "identities": [], 31 | "multitenancy": { 32 | "attribute": null, 33 | "global": null, 34 | "strategy": null 35 | }, 36 | "repo": "Elixir.AshPostgres.TestRepo", 37 | "schema": null, 38 | "table": "orgs" 39 | } -------------------------------------------------------------------------------- /priv/resource_snapshots/test_repo/orgs/20240327211150.json: -------------------------------------------------------------------------------- 1 | { 2 | "attributes": [ 3 | { 4 | "default": "fragment(\"gen_random_uuid()\")", 5 | "size": null, 6 | "type": "uuid", 7 | "source": "id", 8 | "references": null, 9 | "primary_key?": true, 10 | "allow_nil?": false, 11 | "generated?": false 12 | }, 13 | { 14 | "default": "nil", 15 | "size": null, 16 | "type": "text", 17 | "source": "name", 18 | "references": null, 19 | "primary_key?": false, 20 | "allow_nil?": true, 21 | "generated?": false 22 | } 23 | ], 24 | "table": "orgs", 25 | "hash": "964A05962CF0871AF3C96713E799037155149E5E69FAB9A85BD4449F2A075906", 26 | "repo": "Elixir.AshPostgres.TestRepo", 27 | "multitenancy": { 28 | "global": null, 29 | "attribute": null, 30 | "strategy": null 31 | }, 32 | "schema": null, 33 | "identities": [], 34 | "has_create_action": true, 35 | "custom_indexes": [], 36 | "custom_statements": [], 37 | "base_filter": null, 38 | "check_constraints": [] 39 | } -------------------------------------------------------------------------------- /priv/resource_snapshots/test_repo/points/20250313112823.json: -------------------------------------------------------------------------------- 1 | { 2 | "attributes": [ 3 | { 4 | "allow_nil?": false, 5 | "default": "nil", 6 | "generated?": false, 7 | "primary_key?": true, 8 | "references": null, 9 | "size": null, 10 | "source": "id", 11 | "type": [ 12 | "array", 13 | "float" 14 | ] 15 | } 16 | ], 17 | "base_filter": null, 18 | "check_constraints": [], 19 | "custom_indexes": [], 20 | "custom_statements": [], 21 | "has_create_action": true, 22 | "hash": "1E2378D30CF657B673E7EE140FDD4CA067E23FB8862A8F05D35A3F680EBA06B4", 23 | "identities": [ 24 | { 25 | "all_tenants?": false, 26 | "base_filter": null, 27 | "index_name": "points_id_index", 28 | "keys": [ 29 | { 30 | "type": "atom", 31 | "value": "id" 32 | } 33 | ], 34 | "name": "id", 35 | "nils_distinct?": true, 36 | "where": null 37 | } 38 | ], 39 | "multitenancy": { 40 | "attribute": null, 41 | "global": null, 42 | "strategy": null 43 | }, 44 | "repo": "Elixir.AshPostgres.TestRepo", 45 | "schema": null, 46 | "table": "points" 47 | } -------------------------------------------------------------------------------- /priv/resource_snapshots/test_repo/post_views/20230905050351.json: -------------------------------------------------------------------------------- 1 | { 2 | "attributes": [ 3 | { 4 | "default": "fragment(\"now()\")", 5 | "size": null, 6 | "type": "utc_datetime_usec", 7 | "source": "time", 8 | "references": null, 9 | "allow_nil?": false, 10 | "primary_key?": false, 11 | "generated?": false 12 | }, 13 | { 14 | "default": "nil", 15 | "size": null, 16 | "type": "text", 17 | "source": "browser", 18 | "references": null, 19 | "allow_nil?": true, 20 | "primary_key?": false, 21 | "generated?": false 22 | }, 23 | { 24 | "default": "nil", 25 | "size": null, 26 | "type": "uuid", 27 | "source": "post_id", 28 | "references": null, 29 | "allow_nil?": false, 30 | "primary_key?": false, 31 | "generated?": false 32 | } 33 | ], 34 | "table": "post_views", 35 | "hash": "45E84815BD6E5B9617B491C57A429F210D98AE37428AC7C210B2E9D6AEA27DB3", 36 | "repo": "Elixir.AshPostgres.TestRepo", 37 | "schema": null, 38 | "check_constraints": [], 39 | "identities": [], 40 | "custom_indexes": [], 41 | "multitenancy": { 42 | "global": null, 43 | "attribute": null, 44 | "strategy": null 45 | }, 46 | "base_filter": null, 47 | "custom_statements": [], 48 | "has_create_action": true 49 | } -------------------------------------------------------------------------------- /priv/resource_snapshots/test_repo/post_views/20240327211917.json: -------------------------------------------------------------------------------- 1 | { 2 | "attributes": [ 3 | { 4 | "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", 5 | "size": null, 6 | "type": "utc_datetime_usec", 7 | "source": "time", 8 | "references": null, 9 | "primary_key?": false, 10 | "allow_nil?": false, 11 | "generated?": false 12 | }, 13 | { 14 | "default": "nil", 15 | "size": null, 16 | "type": "text", 17 | "source": "browser", 18 | "references": null, 19 | "primary_key?": false, 20 | "allow_nil?": true, 21 | "generated?": false 22 | }, 23 | { 24 | "default": "nil", 25 | "size": null, 26 | "type": "uuid", 27 | "source": "post_id", 28 | "references": null, 29 | "primary_key?": false, 30 | "allow_nil?": false, 31 | "generated?": false 32 | } 33 | ], 34 | "table": "post_views", 35 | "hash": "3DC29B11C7D71D2C29C9FE68F52664F244D6DF2F4372A9F6BD135BD89CAD10B4", 36 | "repo": "Elixir.AshPostgres.TestRepo", 37 | "multitenancy": { 38 | "global": null, 39 | "attribute": null, 40 | "strategy": null 41 | }, 42 | "schema": null, 43 | "identities": [], 44 | "has_create_action": true, 45 | "custom_indexes": [], 46 | "custom_statements": [], 47 | "base_filter": null, 48 | "check_constraints": [] 49 | } -------------------------------------------------------------------------------- /priv/resource_snapshots/test_repo/records/20240109160153.json: -------------------------------------------------------------------------------- 1 | { 2 | "attributes": [ 3 | { 4 | "default": "fragment(\"uuid_generate_v4()\")", 5 | "size": null, 6 | "type": "uuid", 7 | "source": "id", 8 | "references": null, 9 | "allow_nil?": false, 10 | "generated?": false, 11 | "primary_key?": true 12 | }, 13 | { 14 | "default": "nil", 15 | "size": null, 16 | "type": "text", 17 | "source": "full_name", 18 | "references": null, 19 | "allow_nil?": false, 20 | "generated?": false, 21 | "primary_key?": false 22 | }, 23 | { 24 | "default": "fragment(\"now()\")", 25 | "size": null, 26 | "type": "utc_datetime_usec", 27 | "source": "inserted_at", 28 | "references": null, 29 | "allow_nil?": false, 30 | "generated?": false, 31 | "primary_key?": false 32 | }, 33 | { 34 | "default": "fragment(\"now()\")", 35 | "size": null, 36 | "type": "utc_datetime_usec", 37 | "source": "updated_at", 38 | "references": null, 39 | "allow_nil?": false, 40 | "generated?": false, 41 | "primary_key?": false 42 | } 43 | ], 44 | "table": "records", 45 | "hash": "C33734A021F713C5331B6306A94EDFF918808A62860014D0FF1F39DAF323D5A6", 46 | "repo": "Elixir.AshPostgres.TestRepo", 47 | "identities": [], 48 | "schema": null, 49 | "check_constraints": [], 50 | "custom_indexes": [], 51 | "base_filter": null, 52 | "multitenancy": { 53 | "global": null, 54 | "attribute": null, 55 | "strategy": null 56 | }, 57 | "custom_statements": [], 58 | "has_create_action": true 59 | } -------------------------------------------------------------------------------- /priv/resource_snapshots/test_repo/records/20240327211150.json: -------------------------------------------------------------------------------- 1 | { 2 | "attributes": [ 3 | { 4 | "default": "fragment(\"gen_random_uuid()\")", 5 | "size": null, 6 | "type": "uuid", 7 | "source": "id", 8 | "references": null, 9 | "primary_key?": true, 10 | "allow_nil?": false, 11 | "generated?": false 12 | }, 13 | { 14 | "default": "nil", 15 | "size": null, 16 | "type": "text", 17 | "source": "full_name", 18 | "references": null, 19 | "primary_key?": false, 20 | "allow_nil?": false, 21 | "generated?": false 22 | }, 23 | { 24 | "default": "fragment(\"now()\")", 25 | "size": null, 26 | "type": "utc_datetime_usec", 27 | "source": "inserted_at", 28 | "references": null, 29 | "primary_key?": false, 30 | "allow_nil?": false, 31 | "generated?": false 32 | }, 33 | { 34 | "default": "fragment(\"now()\")", 35 | "size": null, 36 | "type": "utc_datetime_usec", 37 | "source": "updated_at", 38 | "references": null, 39 | "primary_key?": false, 40 | "allow_nil?": false, 41 | "generated?": false 42 | } 43 | ], 44 | "table": "records", 45 | "hash": "4947993D236ABA95A54A3AEAB01748894D857344F72470EF124993FD8E098C35", 46 | "repo": "Elixir.AshPostgres.TestRepo", 47 | "multitenancy": { 48 | "global": null, 49 | "attribute": null, 50 | "strategy": null 51 | }, 52 | "schema": null, 53 | "identities": [], 54 | "has_create_action": true, 55 | "custom_indexes": [], 56 | "custom_statements": [], 57 | "base_filter": null, 58 | "check_constraints": [] 59 | } -------------------------------------------------------------------------------- /priv/resource_snapshots/test_repo/schematic_groups/20240821213522.json: -------------------------------------------------------------------------------- 1 | { 2 | "attributes": [ 3 | { 4 | "allow_nil?": false, 5 | "default": "fragment(\"gen_random_uuid()\")", 6 | "generated?": false, 7 | "primary_key?": true, 8 | "references": null, 9 | "size": null, 10 | "source": "id", 11 | "type": "uuid" 12 | } 13 | ], 14 | "base_filter": null, 15 | "check_constraints": [], 16 | "custom_indexes": [], 17 | "custom_statements": [], 18 | "has_create_action": true, 19 | "hash": "F24673A4219DEC6873571CCF68B8F0CC34B5843DAA2D7B71A16EFE576C385C1C", 20 | "identities": [], 21 | "multitenancy": { 22 | "attribute": null, 23 | "global": null, 24 | "strategy": null 25 | }, 26 | "repo": "Elixir.AshPostgres.TestRepo", 27 | "schema": null, 28 | "table": "schematic_groups" 29 | } -------------------------------------------------------------------------------- /priv/resource_snapshots/test_repo/staff_group/20250123164209.json: -------------------------------------------------------------------------------- 1 | { 2 | "attributes": [ 3 | { 4 | "allow_nil?": false, 5 | "default": "fragment(\"gen_random_uuid()\")", 6 | "generated?": false, 7 | "primary_key?": true, 8 | "references": null, 9 | "size": null, 10 | "source": "id", 11 | "type": "uuid" 12 | }, 13 | { 14 | "allow_nil?": false, 15 | "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", 16 | "generated?": false, 17 | "primary_key?": false, 18 | "references": null, 19 | "size": null, 20 | "source": "inserted_at", 21 | "type": "utc_datetime_usec" 22 | }, 23 | { 24 | "allow_nil?": false, 25 | "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", 26 | "generated?": false, 27 | "primary_key?": false, 28 | "references": null, 29 | "size": null, 30 | "source": "updated_at", 31 | "type": "utc_datetime_usec" 32 | } 33 | ], 34 | "base_filter": null, 35 | "check_constraints": [], 36 | "custom_indexes": [], 37 | "custom_statements": [], 38 | "has_create_action": true, 39 | "hash": "4353CC893BCF75218813E38289037CD3F213C4CFC4200426351DDCD5BA48F778", 40 | "identities": [], 41 | "multitenancy": { 42 | "attribute": null, 43 | "global": null, 44 | "strategy": null 45 | }, 46 | "repo": "Elixir.AshPostgres.TestRepo", 47 | "schema": null, 48 | "table": "staff_group" 49 | } -------------------------------------------------------------------------------- /priv/resource_snapshots/test_repo/string_points/20250313112823.json: -------------------------------------------------------------------------------- 1 | { 2 | "attributes": [ 3 | { 4 | "allow_nil?": false, 5 | "default": "nil", 6 | "generated?": false, 7 | "primary_key?": true, 8 | "references": null, 9 | "size": null, 10 | "source": "id", 11 | "type": "text" 12 | } 13 | ], 14 | "base_filter": null, 15 | "check_constraints": [], 16 | "custom_indexes": [], 17 | "custom_statements": [], 18 | "has_create_action": true, 19 | "hash": "43DADF8B94BBDBD802AE3D70ED67791CC968478B7EE6AEBE5B5F8F676DB323BE", 20 | "identities": [ 21 | { 22 | "all_tenants?": false, 23 | "base_filter": null, 24 | "index_name": "string_points_id_index", 25 | "keys": [ 26 | { 27 | "type": "atom", 28 | "value": "id" 29 | } 30 | ], 31 | "name": "id", 32 | "nils_distinct?": true, 33 | "where": null 34 | } 35 | ], 36 | "multitenancy": { 37 | "attribute": null, 38 | "global": null, 39 | "strategy": null 40 | }, 41 | "repo": "Elixir.AshPostgres.TestRepo", 42 | "schema": null, 43 | "table": "string_points" 44 | } -------------------------------------------------------------------------------- /priv/resource_snapshots/test_repo/subquery_child/20240130133933.json: -------------------------------------------------------------------------------- 1 | { 2 | "attributes": [ 3 | { 4 | "default": "fragment(\"uuid_generate_v4()\")", 5 | "size": null, 6 | "type": "uuid", 7 | "source": "id", 8 | "references": null, 9 | "allow_nil?": false, 10 | "generated?": false, 11 | "primary_key?": true 12 | }, 13 | { 14 | "default": "nil", 15 | "size": null, 16 | "type": "text", 17 | "source": "state", 18 | "references": null, 19 | "allow_nil?": true, 20 | "generated?": false, 21 | "primary_key?": false 22 | } 23 | ], 24 | "table": "subquery_child", 25 | "hash": "14EB56AF533B090146C1A6FA930E83747D6D2765EE518576A4D6329B5CFA2B9E", 26 | "repo": "Elixir.AshPostgres.TestRepo", 27 | "identities": [], 28 | "schema": null, 29 | "check_constraints": [], 30 | "custom_indexes": [], 31 | "base_filter": null, 32 | "multitenancy": { 33 | "global": null, 34 | "attribute": null, 35 | "strategy": null 36 | }, 37 | "custom_statements": [], 38 | "has_create_action": true 39 | } -------------------------------------------------------------------------------- /priv/resource_snapshots/test_repo/subquery_parent/20240130133933.json: -------------------------------------------------------------------------------- 1 | { 2 | "attributes": [ 3 | { 4 | "default": "fragment(\"uuid_generate_v4()\")", 5 | "size": null, 6 | "type": "uuid", 7 | "source": "id", 8 | "references": null, 9 | "allow_nil?": false, 10 | "generated?": false, 11 | "primary_key?": true 12 | }, 13 | { 14 | "default": "nil", 15 | "size": null, 16 | "type": "text", 17 | "source": "owner_email", 18 | "references": null, 19 | "allow_nil?": true, 20 | "generated?": false, 21 | "primary_key?": false 22 | }, 23 | { 24 | "default": "nil", 25 | "size": null, 26 | "type": "text", 27 | "source": "other_owner_email", 28 | "references": null, 29 | "allow_nil?": true, 30 | "generated?": false, 31 | "primary_key?": false 32 | }, 33 | { 34 | "default": "nil", 35 | "size": null, 36 | "type": "boolean", 37 | "source": "visible", 38 | "references": null, 39 | "allow_nil?": true, 40 | "generated?": false, 41 | "primary_key?": false 42 | } 43 | ], 44 | "table": "subquery_parent", 45 | "hash": "4F7C9D2564C1C54AB4E45BE8A5209B764EDC05F060C9B9D3E8EBE8A134FAF797", 46 | "repo": "Elixir.AshPostgres.TestRepo", 47 | "identities": [], 48 | "schema": null, 49 | "check_constraints": [], 50 | "custom_indexes": [], 51 | "base_filter": null, 52 | "multitenancy": { 53 | "global": null, 54 | "attribute": null, 55 | "strategy": null 56 | }, 57 | "custom_statements": [], 58 | "has_create_action": true 59 | } -------------------------------------------------------------------------------- /priv/resource_snapshots/test_repo/temp.temp_entities/20240327211150.json: -------------------------------------------------------------------------------- 1 | { 2 | "attributes": [ 3 | { 4 | "default": "fragment(\"gen_random_uuid()\")", 5 | "size": null, 6 | "type": "uuid", 7 | "source": "id", 8 | "references": null, 9 | "primary_key?": true, 10 | "allow_nil?": false, 11 | "generated?": false 12 | }, 13 | { 14 | "default": "nil", 15 | "size": null, 16 | "type": "text", 17 | "source": "full_name", 18 | "references": null, 19 | "primary_key?": false, 20 | "allow_nil?": false, 21 | "generated?": false 22 | }, 23 | { 24 | "default": "fragment(\"now()\")", 25 | "size": null, 26 | "type": "utc_datetime_usec", 27 | "source": "inserted_at", 28 | "references": null, 29 | "primary_key?": false, 30 | "allow_nil?": false, 31 | "generated?": false 32 | }, 33 | { 34 | "default": "fragment(\"now()\")", 35 | "size": null, 36 | "type": "utc_datetime_usec", 37 | "source": "updated_at", 38 | "references": null, 39 | "primary_key?": false, 40 | "allow_nil?": false, 41 | "generated?": false 42 | } 43 | ], 44 | "table": "temp_entities", 45 | "hash": "B6BCD5D4F436B5B11C6502095CC3157DC75E3AEE723AAC8A8455500D59F1F8EE", 46 | "repo": "Elixir.AshPostgres.TestRepo", 47 | "multitenancy": { 48 | "global": null, 49 | "attribute": null, 50 | "strategy": null 51 | }, 52 | "schema": "temp", 53 | "identities": [], 54 | "has_create_action": true, 55 | "custom_indexes": [], 56 | "custom_statements": [], 57 | "base_filter": null, 58 | "check_constraints": [] 59 | } -------------------------------------------------------------------------------- /priv/resource_snapshots/test_repo/temp_entities/20240109160153.json: -------------------------------------------------------------------------------- 1 | { 2 | "attributes": [ 3 | { 4 | "default": "fragment(\"uuid_generate_v4()\")", 5 | "size": null, 6 | "type": "uuid", 7 | "source": "id", 8 | "references": null, 9 | "allow_nil?": false, 10 | "generated?": false, 11 | "primary_key?": true 12 | }, 13 | { 14 | "default": "nil", 15 | "size": null, 16 | "type": "text", 17 | "source": "full_name", 18 | "references": null, 19 | "allow_nil?": false, 20 | "generated?": false, 21 | "primary_key?": false 22 | }, 23 | { 24 | "default": "fragment(\"now()\")", 25 | "size": null, 26 | "type": "utc_datetime_usec", 27 | "source": "inserted_at", 28 | "references": null, 29 | "allow_nil?": false, 30 | "generated?": false, 31 | "primary_key?": false 32 | }, 33 | { 34 | "default": "fragment(\"now()\")", 35 | "size": null, 36 | "type": "utc_datetime_usec", 37 | "source": "updated_at", 38 | "references": null, 39 | "allow_nil?": false, 40 | "generated?": false, 41 | "primary_key?": false 42 | } 43 | ], 44 | "table": "temp_entities", 45 | "hash": "8E78A1B3B35715F479FDA45768524A8B1F707EDF833F2B8E9A128C13FE719995", 46 | "repo": "Elixir.AshPostgres.TestRepo", 47 | "identities": [], 48 | "schema": "temp", 49 | "check_constraints": [], 50 | "custom_indexes": [], 51 | "base_filter": null, 52 | "multitenancy": { 53 | "global": null, 54 | "attribute": null, 55 | "strategy": null 56 | }, 57 | "custom_statements": [], 58 | "has_create_action": false 59 | } -------------------------------------------------------------------------------- /priv/resource_snapshots/test_repo/user_invites/20240727145758.json: -------------------------------------------------------------------------------- 1 | { 2 | "attributes": [ 3 | { 4 | "allow_nil?": false, 5 | "default": "fragment(\"gen_random_uuid()\")", 6 | "generated?": false, 7 | "primary_key?": true, 8 | "references": null, 9 | "size": null, 10 | "source": "id", 11 | "type": "uuid" 12 | }, 13 | { 14 | "allow_nil?": false, 15 | "default": "nil", 16 | "generated?": false, 17 | "primary_key?": false, 18 | "references": null, 19 | "size": null, 20 | "source": "name", 21 | "type": "text" 22 | }, 23 | { 24 | "allow_nil?": false, 25 | "default": "nil", 26 | "generated?": false, 27 | "primary_key?": false, 28 | "references": null, 29 | "size": null, 30 | "source": "role", 31 | "type": "text" 32 | } 33 | ], 34 | "base_filter": null, 35 | "check_constraints": [], 36 | "custom_indexes": [], 37 | "custom_statements": [], 38 | "has_create_action": true, 39 | "hash": "E2260A6737875A739EB4AFA8896BE4E95F3DC5A624E701AA5F1D09D68C315BBD", 40 | "identities": [], 41 | "multitenancy": { 42 | "attribute": null, 43 | "global": null, 44 | "strategy": null 45 | }, 46 | "repo": "Elixir.AshPostgres.TestRepo", 47 | "schema": null, 48 | "table": "user_invites" 49 | } -------------------------------------------------------------------------------- /priv/test_no_sandbox_repo/migrations/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ash-project/ash_postgres/5b8f46ffb52378a818dee7278d9ab9702dc12121/priv/test_no_sandbox_repo/migrations/.gitkeep -------------------------------------------------------------------------------- /priv/test_repo/migrations/20220914104733_migrate_resources2.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources2 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | alter table(:authors) do 12 | add :badges, {:array, :text} 13 | end 14 | end 15 | 16 | def down do 17 | alter table(:authors) do 18 | remove :badges 19 | end 20 | end 21 | end -------------------------------------------------------------------------------- /priv/test_repo/migrations/20221017133955_migrate_resources3.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources3 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | create unique_index(:post_links, [:source_post_id, :destination_post_id], 12 | name: "post_links_unique_link_index" 13 | ) 14 | end 15 | 16 | def down do 17 | drop_if_exists unique_index(:post_links, [:source_post_id, :destination_post_id], 18 | name: "post_links_unique_link_index" 19 | ) 20 | end 21 | end -------------------------------------------------------------------------------- /priv/test_repo/migrations/20221125171148_migrate_resources4.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources4 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | alter table(:posts) do 12 | add :uniq_custom_one, :text 13 | add :uniq_custom_two, :text 14 | end 15 | end 16 | 17 | def down do 18 | alter table(:posts) do 19 | remove :uniq_custom_two 20 | remove :uniq_custom_one 21 | end 22 | end 23 | end -------------------------------------------------------------------------------- /priv/test_repo/migrations/20221125171150_migrate_resources5.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources5 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | @disable_ddl_transaction true 11 | @disable_migration_lock true 12 | 13 | def up do 14 | create index(:posts, ["uniq_custom_one", "uniq_custom_two"], unique: true, concurrently: true) 15 | end 16 | 17 | def down do 18 | drop_if_exists index(:posts, ["uniq_custom_one", "uniq_custom_two"], 19 | name: "posts_uniq_custom_one_uniq_custom_two_index" 20 | ) 21 | end 22 | end -------------------------------------------------------------------------------- /priv/test_repo/migrations/20221202194704_migrate_resources6.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources6 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | alter table(:post_links) do 12 | add :state, :text, default: "active" 13 | end 14 | end 15 | 16 | def down do 17 | alter table(:post_links) do 18 | remove :state 19 | end 20 | end 21 | end -------------------------------------------------------------------------------- /priv/test_repo/migrations/20221217123726_migrate_resources7.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources7 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | alter table(:users) do 12 | add :is_active, :boolean 13 | end 14 | 15 | create table(:accounts, primary_key: false) do 16 | add :id, :uuid, null: false, default: fragment("uuid_generate_v4()"), primary_key: true 17 | add :is_active, :boolean 18 | 19 | add :user_id, 20 | references(:users, 21 | column: :id, 22 | name: "accounts_user_id_fkey", 23 | type: :uuid, 24 | prefix: "public" 25 | ) 26 | end 27 | end 28 | 29 | def down do 30 | drop constraint(:accounts, "accounts_user_id_fkey") 31 | 32 | drop table(:accounts) 33 | 34 | alter table(:users) do 35 | remove :is_active 36 | end 37 | end 38 | end -------------------------------------------------------------------------------- /priv/test_repo/migrations/20230526144249_migrate_resources9.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources9 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | create table(:managers, primary_key: false) do 12 | add :id, :uuid, null: false, default: fragment("uuid_generate_v4()"), primary_key: true 13 | add :name, :text 14 | add :code, :text, null: false 15 | add :must_be_present, :text, null: false 16 | add :role, :text 17 | 18 | add :organization_id, 19 | references(:orgs, 20 | column: :id, 21 | name: "managers_organization_id_fkey", 22 | type: :uuid, 23 | prefix: "public" 24 | ) 25 | end 26 | 27 | create unique_index(:managers, [:code], name: "managers_uniq_code_index") 28 | end 29 | 30 | def down do 31 | drop_if_exists unique_index(:managers, [:code], name: "managers_uniq_code_index") 32 | 33 | drop constraint(:managers, "managers_organization_id_fkey") 34 | 35 | drop table(:managers) 36 | end 37 | end -------------------------------------------------------------------------------- /priv/test_repo/migrations/20230804223759_install_demo-functions_v0_extension.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.InstallDemoFunctionsV0 do 2 | @moduledoc """ 3 | Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | execute(""" 12 | CREATE OR REPLACE FUNCTION ash_demo_functions() 13 | RETURNS boolean AS $$ SELECT TRUE $$ 14 | LANGUAGE SQL 15 | IMMUTABLE; 16 | """) 17 | end 18 | 19 | def down do 20 | # Uncomment this if you actually want to uninstall the extensions 21 | # when this migration is rolled back: 22 | execute(""" 23 | DROP FUNCTION IF EXISTS ash_demo_functions() 24 | """) 25 | end 26 | end -------------------------------------------------------------------------------- /priv/test_repo/migrations/20230804223818_install_demo-functions_v1_extension.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.InstallDemoFunctionsV1 do 2 | @moduledoc """ 3 | Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | execute(""" 12 | CREATE OR REPLACE FUNCTION ash_demo_functions() 13 | RETURNS boolean AS $$ SELECT FALSE $$ 14 | LANGUAGE SQL 15 | IMMUTABLE; 16 | """) 17 | end 18 | 19 | def down do 20 | # Uncomment this if you actually want to uninstall the extensions 21 | # when this migration is rolled back: 22 | execute(""" 23 | DROP FUNCTION IF EXISTS ash_demo_functions() 24 | """) 25 | end 26 | end -------------------------------------------------------------------------------- /priv/test_repo/migrations/20230823161017_migrate_resources10.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources10 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | alter table(:posts) do 12 | add :stuff, :map 13 | end 14 | end 15 | 16 | def down do 17 | alter table(:posts) do 18 | remove :stuff 19 | end 20 | end 21 | end -------------------------------------------------------------------------------- /priv/test_repo/migrations/20230905050351_add_post_views.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.AddPostViews do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | create table(:post_views, primary_key: false) do 12 | add :time, :utc_datetime_usec, null: false, default: fragment("now()") 13 | add :browser, :text 14 | add :post_id, :uuid, null: false 15 | end 16 | end 17 | 18 | def down do 19 | drop table(:post_views) 20 | end 21 | end -------------------------------------------------------------------------------- /priv/test_repo/migrations/20231127212608_add_composite_type.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.AddCompositeType do 2 | use Ecto.Migration 3 | 4 | def change do 5 | execute(""" 6 | CREATE TYPE custom_point AS ( 7 | x bigint, 8 | y bigint 9 | ); 10 | """, 11 | """ 12 | DROP TYPE custom_point; 13 | """) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20231127215636_migrate_resources11.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources11 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | alter table(:posts) do 12 | add :composite_point, :custom_point 13 | end 14 | end 15 | 16 | def down do 17 | alter table(:posts) do 18 | remove :composite_point 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20231129141453_migrate_resources12.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources12 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | alter table(:posts) do 12 | modify :composite_point, :custom_point 13 | end 14 | end 15 | 16 | def down do 17 | alter table(:posts) do 18 | modify :composite_point, :point 19 | end 20 | end 21 | end -------------------------------------------------------------------------------- /priv/test_repo/migrations/20231214220937_install_ash-functions_extension_2.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.InstallAshFunctionsExtension2 do 2 | @moduledoc """ 3 | Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | execute(""" 12 | CREATE OR REPLACE FUNCTION ash_raise_error(json_data jsonb) 13 | RETURNS BOOLEAN AS $$ 14 | BEGIN 15 | -- Raise an error with the provided JSON data. 16 | -- The JSON object is converted to text for inclusion in the error message. 17 | RAISE EXCEPTION '%', json_data::text; 18 | RETURN NULL; 19 | END; 20 | $$ LANGUAGE plpgsql; 21 | """) 22 | 23 | execute(""" 24 | CREATE OR REPLACE FUNCTION ash_raise_error(json_data jsonb, type_signal ANYCOMPATIBLE) 25 | RETURNS ANYCOMPATIBLE AS $$ 26 | BEGIN 27 | -- Raise an error with the provided JSON data. 28 | -- The JSON object is converted to text for inclusion in the error message. 29 | RAISE EXCEPTION '%', json_data::text; 30 | RETURN NULL; 31 | END; 32 | $$ LANGUAGE plpgsql; 33 | """) 34 | end 35 | 36 | def down do 37 | # Uncomment this if you actually want to uninstall the extensions 38 | # when this migration is rolled back: 39 | execute( 40 | "DROP FUNCTION IF EXISTS ash_raise_error(jsonb), ash_raise_error(jsonb, ANYCOMPATIBLE)" 41 | ) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20231219132807_migrate_resources13.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources13 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | rename table(:posts), :title, to: :title_column 12 | end 13 | 14 | def down do 15 | rename table(:posts), :title_column, to: :title 16 | end 17 | end -------------------------------------------------------------------------------- /priv/test_repo/migrations/20240109155951_create_temp_schema.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.CreateTempSchema do 2 | use Ecto.Migration 3 | 4 | def up do 5 | execute("create schema if not exists \"temp\"") 6 | end 7 | 8 | def down do 9 | execute("drop schema if exists \"temp\"") 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20240129221511_migrate_resources15.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources15 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | alter table(:posts) do 12 | add :list_containing_nils, {:array, :text} 13 | end 14 | end 15 | 16 | def down do 17 | alter table(:posts) do 18 | remove :list_containing_nils 19 | end 20 | end 21 | end -------------------------------------------------------------------------------- /priv/test_repo/migrations/20240224001913_migrate_resources16.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources16 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | alter table(:posts) do 12 | add :list_of_stuff, {:array, :map} 13 | end 14 | end 15 | 16 | def down do 17 | alter table(:posts) do 18 | remove :list_of_stuff 19 | end 20 | end 21 | end -------------------------------------------------------------------------------- /priv/test_repo/migrations/20240227180858_migrate_resources17.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources17 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | create table(:post_followers, primary_key: false) do 12 | add :id, :uuid, null: false, default: fragment("uuid_generate_v4()"), primary_key: true 13 | 14 | add :post_id, 15 | references(:posts, 16 | column: :id, 17 | name: "post_followers_post_id_fkey", 18 | type: :uuid, 19 | prefix: "public" 20 | ), 21 | null: false 22 | 23 | add :user_id, 24 | references(:users, 25 | column: :id, 26 | name: "post_followers_user_id_fkey", 27 | type: :uuid, 28 | prefix: "public" 29 | ), 30 | null: false 31 | end 32 | end 33 | 34 | def down do 35 | drop constraint(:post_followers, "post_followers_post_id_fkey") 36 | 37 | drop constraint(:post_followers, "post_followers_user_id_fkey") 38 | 39 | drop table(:post_followers) 40 | end 41 | end -------------------------------------------------------------------------------- /priv/test_repo/migrations/20240227181137_migrate_resources18.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources18 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | rename table(:post_followers), :user_id, to: :follower_id 12 | 13 | drop constraint(:post_followers, "post_followers_user_id_fkey") 14 | 15 | alter table(:post_followers) do 16 | modify :follower_id, 17 | references(:users, 18 | column: :id, 19 | name: "post_followers_follower_id_fkey", 20 | type: :uuid, 21 | prefix: "public" 22 | ) 23 | end 24 | 25 | execute( 26 | "ALTER TABLE post_followers alter CONSTRAINT post_followers_follower_id_fkey NOT DEFERRABLE" 27 | ) 28 | end 29 | 30 | def down do 31 | drop constraint(:post_followers, "post_followers_follower_id_fkey") 32 | 33 | alter table(:post_followers) do 34 | modify :user_id, 35 | references(:users, 36 | column: :id, 37 | name: "post_followers_user_id_fkey", 38 | type: :uuid, 39 | prefix: "public" 40 | ) 41 | end 42 | 43 | rename table(:post_followers), :follower_id, to: :user_id 44 | end 45 | end -------------------------------------------------------------------------------- /priv/test_repo/migrations/20240503012410_migrate_resources21.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources21 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | alter table(:posts) do 12 | add(:datetime, :utc_datetime_usec) 13 | end 14 | end 15 | 16 | def down do 17 | alter table(:posts) do 18 | remove(:datetime) 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20240504185511_migrate_resources22.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources22 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | alter table(:posts) do 12 | modify(:updated_at, :"timestamptz(6)") 13 | modify(:datetime, :"timestamptz(6)") 14 | end 15 | end 16 | 17 | def down do 18 | alter table(:posts) do 19 | modify(:datetime, :utc_datetime_usec) 20 | modify(:updated_at, :utc_datetime_usec) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20240516205244_migrate_resources23.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources23 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | drop(constraint("post_followers", "post_followers_pkey")) 12 | 13 | alter table(:post_followers) do 14 | modify(:follower_id, :uuid, primary_key: true) 15 | modify(:post_id, :uuid, primary_key: true) 16 | 17 | remove(:id) 18 | end 19 | end 20 | 21 | def down do 22 | drop(constraint("post_followers", "post_followers_pkey")) 23 | 24 | alter table(:post_followers) do 25 | add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) 26 | modify(:post_id, :uuid, primary_key: false) 27 | modify(:follower_id, :uuid, primary_key: false) 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20240517223946_migrate_resources24.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources24 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | alter table(:post_followers) do 12 | add(:order, :bigint) 13 | end 14 | end 15 | 16 | def down do 17 | alter table(:post_followers) do 18 | remove(:order) 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20240524031113_migrate_resources25.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources25 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | alter table(:posts) do 12 | add(:uniq_on_upper, :text) 13 | end 14 | 15 | create( 16 | unique_index(:posts, ["UPPER(uniq_on_upper)"], 17 | where: "type = 'sponsored'", 18 | name: "posts_uniq_on_upper_index" 19 | ) 20 | ) 21 | end 22 | 23 | def down do 24 | drop_if_exists( 25 | unique_index(:posts, ["UPPER(uniq_on_upper)"], name: "posts_uniq_on_upper_index") 26 | ) 27 | 28 | alter table(:posts) do 29 | remove(:uniq_on_upper) 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20240524041750_migrate_resources26.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources26 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | alter table(:posts) do 12 | add(:uniq_if_contains_foo, :text) 13 | end 14 | 15 | drop_if_exists( 16 | unique_index(:posts, [:"UPPER(uniq_on_upper)"], name: "posts_uniq_on_upper_index") 17 | ) 18 | 19 | create( 20 | unique_index(:posts, ["UPPER(uniq_on_upper)"], 21 | where: "type = 'sponsored'", 22 | name: "posts_uniq_on_upper_index" 23 | ) 24 | ) 25 | 26 | create( 27 | unique_index(:posts, [:uniq_if_contains_foo], 28 | name: "posts_uniq_if_contains_foo_index", 29 | where: "((uniq_if_contains_foo LIKE '%foo%')) AND (type = 'sponsored')" 30 | ) 31 | ) 32 | end 33 | 34 | def down do 35 | drop_if_exists( 36 | unique_index(:posts, [:uniq_if_contains_foo], name: "posts_uniq_if_contains_foo_index") 37 | ) 38 | 39 | drop_if_exists( 40 | unique_index(:posts, ["UPPER(uniq_on_upper)"], name: "posts_uniq_on_upper_index") 41 | ) 42 | 43 | create( 44 | unique_index(:posts, [:"UPPER(uniq_on_upper)"], 45 | where: "type = 'sponsored'", 46 | name: "posts_uniq_on_upper_index" 47 | ) 48 | ) 49 | 50 | alter table(:posts) do 51 | remove(:uniq_if_contains_foo) 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20240610195853_migrate_resources27.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources27 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | drop_if_exists( 12 | unique_index(:post_links, ["source_post_id", "destination_post_id"], 13 | name: "post_links_unique_link_index" 14 | ) 15 | ) 16 | 17 | create( 18 | unique_index(:post_links, ["destination_post_id", "source_post_id"], 19 | name: "post_links_unique_link_index" 20 | ) 21 | ) 22 | end 23 | 24 | def down do 25 | drop_if_exists( 26 | unique_index(:post_links, ["destination_post_id", "source_post_id"], 27 | name: "post_links_unique_link_index" 28 | ) 29 | ) 30 | 31 | create( 32 | unique_index(:post_links, ["source_post_id", "destination_post_id"], 33 | name: "post_links_unique_link_index" 34 | ) 35 | ) 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20240618102809_migrate_resources30.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources30 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | alter table(:posts) do 12 | add( 13 | :parent_post_id, 14 | references(:posts, 15 | column: :id, 16 | name: "posts_parent_post_id_fkey", 17 | type: :uuid, 18 | prefix: "public" 19 | ) 20 | ) 21 | end 22 | end 23 | 24 | def down do 25 | drop(constraint(:posts, "posts_parent_post_id_fkey")) 26 | 27 | alter table(:posts) do 28 | remove(:parent_post_id) 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20240627223225_migrate_resources31.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources31 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | alter table(:multitenant_orgs) do 12 | add( 13 | :owner_id, 14 | references(:users, 15 | column: :id, 16 | name: "multitenant_orgs_owner_id_fkey", 17 | type: :uuid, 18 | prefix: "public" 19 | ) 20 | ) 21 | end 22 | end 23 | 24 | def down do 25 | drop(constraint(:multitenant_orgs, "multitenant_orgs_owner_id_fkey")) 26 | 27 | alter table(:multitenant_orgs) do 28 | remove(:owner_id) 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20240703155134_migrate_resources32.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources32 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | alter table(:multitenant_orgs) do 12 | modify(:owner_id, :uuid) 13 | end 14 | end 15 | 16 | def down do 17 | alter table(:multitenant_orgs) do 18 | modify(:owner_id, :text) 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20240705113722_migrate_resources33.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources33 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | alter table(:authors) do 12 | add(:bios, :jsonb) 13 | end 14 | end 15 | 16 | def down do 17 | alter table(:authors) do 18 | remove(:bios) 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20240712232026_migrate_resources34.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources34 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | alter table(:posts) do 12 | add(:constrained_int, :bigint) 13 | end 14 | end 15 | 16 | def down do 17 | alter table(:posts) do 18 | remove(:constrained_int) 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20240715135403_migrate_resources35.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources35 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | alter table(:posts) do 12 | modify(:constrained_int, :bigint, null: false, default: 2) 13 | end 14 | end 15 | 16 | def down do 17 | alter table(:posts) do 18 | modify(:constrained_int, :bigint, null: true, default: nil) 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20240717104854_no_attributes_calculation_test.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.NoAttributesCalculationTest do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | alter table(:items) do 12 | add(:key, :text) 13 | add(:value, :bigint) 14 | end 15 | end 16 | 17 | def down do 18 | alter table(:items) do 19 | remove(:value) 20 | remove(:key) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20240717151815_migrate_resources36.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources36 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | create(unique_index(:other_items, [:item_id], name: "other_items_unique_parent_index")) 12 | end 13 | 14 | def down do 15 | drop_if_exists( 16 | unique_index(:other_items, [:item_id], name: "other_items_unique_parent_index") 17 | ) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20240717153736_migrate_resources37.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources37 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | create table(:relationship_items, primary_key: false) do 12 | add(:id, :uuid, null: false, default: fragment("uuid_generate_v7()"), primary_key: true) 13 | add(:key, :text, null: false) 14 | add(:value, :bigint, null: false) 15 | 16 | add(:inserted_at, :utc_datetime_usec, 17 | null: false, 18 | default: fragment("(now() AT TIME ZONE 'utc')") 19 | ) 20 | 21 | add(:updated_at, :utc_datetime_usec, 22 | null: false, 23 | default: fragment("(now() AT TIME ZONE 'utc')") 24 | ) 25 | end 26 | 27 | alter table(:items) do 28 | remove(:value) 29 | end 30 | end 31 | 32 | def down do 33 | alter table(:items) do 34 | add(:value, :bigint) 35 | end 36 | 37 | drop(table(:relationship_items)) 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20240727145758_user_invites.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.UserInvites do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | alter table(:users) do 12 | add(:role, :text, default: "user") 13 | end 14 | 15 | create table(:user_invites, primary_key: false) do 16 | add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) 17 | add(:name, :text, null: false) 18 | add(:role, :text, null: false) 19 | end 20 | end 21 | 22 | def down do 23 | drop(table(:user_invites)) 24 | 25 | alter table(:users) do 26 | remove(:role) 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20240906170759_migrate_resources38.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources38 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | create table(:post_permalinks, primary_key: false) do 12 | add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) 13 | 14 | add( 15 | :post_id, 16 | references(:posts, 17 | column: :id, 18 | name: "post_permalinks_post_id_fkey", 19 | type: :uuid, 20 | prefix: "public", 21 | on_delete: :nothing 22 | ), 23 | null: false 24 | ) 25 | end 26 | end 27 | 28 | def down do 29 | drop(constraint(:post_permalinks, "post_permalinks_post_id_fkey")) 30 | 31 | drop(table(:post_permalinks)) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20240910180107_migrate_resources39.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources39 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | alter table(:posts) do 12 | add(:not_selected_by_default, :text) 13 | end 14 | end 15 | 16 | def down do 17 | alter table(:posts) do 18 | remove(:not_selected_by_default) 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20240911225319_install_1_extensions.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.Install1Extensions20240911225317 do 2 | @moduledoc """ 3 | Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | execute("CREATE EXTENSION IF NOT EXISTS \"ltree\"") 12 | end 13 | 14 | def down do 15 | # Uncomment this if you actually want to uninstall the extensions 16 | # when this migration is rolled back: 17 | # execute("DROP EXTENSION IF EXISTS \"ltree\"") 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20240911225320_migrate_resources40.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources40 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | alter table(:posts) do 12 | add(:ltree_unescaped, :ltree) 13 | add(:ltree_escaped, :ltree) 14 | end 15 | end 16 | 17 | def down do 18 | alter table(:posts) do 19 | remove(:ltree_escaped) 20 | remove(:ltree_unescaped) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20240918104740_migrate_resources41.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources41 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | alter table(:posts) do 12 | add(:version, :bigint, null: false, default: 1) 13 | end 14 | end 15 | 16 | def down do 17 | alter table(:posts) do 18 | remove(:version) 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20240929121224_migrate_resources42.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources42 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | alter table(:posts) do 12 | add(:limited_score, :bigint) 13 | end 14 | end 15 | 16 | def down do 17 | alter table(:posts) do 18 | remove(:limited_score) 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20240929124728_migrate_resources43.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources43 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | alter table(:users) do 12 | add(:role_list, {:array, :text}, default: []) 13 | end 14 | end 15 | 16 | def down do 17 | alter table(:users) do 18 | remove(:role_list) 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20241208221219_migrate_resources44.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources44 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | create table(:co_authored_posts, primary_key: false) do 12 | add(:role, :text, null: false) 13 | add(:was_cancelled_at, :utc_datetime) 14 | 15 | add( 16 | :author_id, 17 | references(:authors, 18 | column: :id, 19 | name: "co_authored_posts_author_id_fkey", 20 | type: :uuid, 21 | prefix: "public" 22 | ), 23 | primary_key: true, 24 | null: false 25 | ) 26 | 27 | add( 28 | :post_id, 29 | references(:posts, 30 | column: :id, 31 | name: "co_authored_posts_post_id_fkey", 32 | type: :uuid, 33 | prefix: "public" 34 | ), 35 | primary_key: true, 36 | null: false 37 | ) 38 | end 39 | end 40 | 41 | def down do 42 | drop(constraint(:co_authored_posts, "co_authored_posts_author_id_fkey")) 43 | 44 | drop(constraint(:co_authored_posts, "co_authored_posts_post_id_fkey")) 45 | 46 | drop(table(:co_authored_posts)) 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20250122190558_migrate_resources46.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources46 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | create table(:non_multitenant_post_links, primary_key: false) do 12 | add(:state, :text, default: "active") 13 | add(:source_id, :uuid, primary_key: true, null: false) 14 | 15 | add( 16 | :dest_id, 17 | references(:posts, 18 | column: :id, 19 | name: "non_multitenant_post_links_dest_id_fkey", 20 | type: :uuid, 21 | prefix: "public" 22 | ), 23 | primary_key: true, 24 | null: false 25 | ) 26 | end 27 | 28 | create( 29 | unique_index(:non_multitenant_post_links, [:source_id, :dest_id], 30 | name: "non_multitenant_post_links_unique_link_index" 31 | ) 32 | ) 33 | end 34 | 35 | def down do 36 | drop_if_exists( 37 | unique_index(:non_multitenant_post_links, [:source_id, :dest_id], 38 | name: "non_multitenant_post_links_unique_link_index" 39 | ) 40 | ) 41 | 42 | drop(constraint(:non_multitenant_post_links, "non_multitenant_post_links_source_id_fkey")) 43 | 44 | drop(constraint(:non_multitenant_post_links, "non_multitenant_post_links_dest_id_fkey")) 45 | 46 | drop(table(:non_multitenant_post_links)) 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20250123161002_migrate_resources47.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources47 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | create table(:comment_links, primary_key: false) do 12 | add( 13 | :source_id, 14 | references(:comments, 15 | column: :id, 16 | name: "comment_links_source_id_fkey", 17 | type: :uuid, 18 | prefix: "public" 19 | ), 20 | primary_key: true, 21 | null: false 22 | ) 23 | 24 | add( 25 | :dest_id, 26 | references(:comments, 27 | column: :id, 28 | name: "comment_links_dest_id_fkey", 29 | type: :uuid, 30 | prefix: "public" 31 | ), 32 | primary_key: true, 33 | null: false 34 | ) 35 | end 36 | 37 | create( 38 | unique_index(:comment_links, [:source_id, :dest_id], 39 | name: "comment_links_unique_link_index" 40 | ) 41 | ) 42 | end 43 | 44 | def down do 45 | drop_if_exists( 46 | unique_index(:comment_links, [:source_id, :dest_id], 47 | name: "comment_links_unique_link_index" 48 | ) 49 | ) 50 | 51 | drop(constraint(:comment_links, "comment_links_source_id_fkey")) 52 | 53 | drop(constraint(:comment_links, "comment_links_dest_id_fkey")) 54 | 55 | drop(table(:comment_links)) 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20250210191116_migrate_resources49.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources49 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | alter table(:orgs) do 12 | add(:department, :text) 13 | end 14 | 15 | create(unique_index(:orgs, ["(LOWER(department))"], name: "orgs_department_index")) 16 | end 17 | 18 | def down do 19 | drop_if_exists(unique_index(:orgs, ["(LOWER(department))"], name: "orgs_department_index")) 20 | 21 | alter table(:orgs) do 22 | remove(:department) 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20250217054207_migrate_resources50.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources50 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | alter table(:posts) do 12 | add(:metadata, :map) 13 | end 14 | end 15 | 16 | def down do 17 | alter table(:posts) do 18 | remove(:metadata) 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20250320225052_add_csv_resource.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.AddCsvResource do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | alter table(:users) do 12 | remove(:org_id) 13 | modify(:role_list, {:array, :text}, null: false) 14 | modify(:role, :text, null: false) 15 | end 16 | 17 | create table(:csv, primary_key: false) do 18 | add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) 19 | add(:column_mapping_embedded, :jsonb) 20 | add(:column_mapping_new_type, :jsonb) 21 | end 22 | end 23 | 24 | def down do 25 | drop(table(:csv)) 26 | 27 | alter table(:users) do 28 | modify(:role, :text, null: true) 29 | modify(:role_list, {:array, :text}, null: true) 30 | 31 | add( 32 | :org_id, 33 | references(:multitenant_orgs, 34 | column: :id, 35 | name: "users_org_id_fkey", 36 | type: :uuid, 37 | prefix: "public" 38 | ) 39 | ) 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20250321142835_migrate_resources52.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources52 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | alter table(:users) do 12 | modify(:role_list, {:array, :text}, null: true) 13 | modify(:role, :text, null: true) 14 | 15 | add( 16 | :org_id, 17 | references(:multitenant_orgs, 18 | column: :id, 19 | name: "users_org_id_fkey", 20 | type: :uuid, 21 | prefix: "public" 22 | ) 23 | ) 24 | end 25 | end 26 | 27 | def down do 28 | drop(constraint(:users, "users_org_id_fkey")) 29 | 30 | alter table(:users) do 31 | remove(:org_id) 32 | modify(:role, :text, null: false) 33 | modify(:role_list, {:array, :text}, null: false) 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20250519103535_migrate_resources53.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources53 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | create table(:multitenant_named_orgs, primary_key: false) do 12 | add(:name, :text, null: false, primary_key: true) 13 | end 14 | end 15 | 16 | def down do 17 | drop(table(:multitenant_named_orgs)) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20250520130634_migrate_resources54.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.MigrateResources54 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | alter table(:posts) do 12 | add(:person_detail, :map) 13 | end 14 | end 15 | 16 | def down do 17 | alter table(:posts) do 18 | remove(:person_detail) 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20250521105654_add_model_tuple_to_post.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.AddModelTupleToPost do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | alter table(:posts) do 12 | add(:model, :map, null: false) 13 | end 14 | end 15 | 16 | def down do 17 | alter table(:posts) do 18 | remove(:model) 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20250605230457_create_record_temp_entities_table.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.Migrations.CreateRecordTempEntitiesTable do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | create table(:records_temp_entities, primary_key: false) do 12 | add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) 13 | 14 | add( 15 | :record_id, 16 | references(:records, 17 | column: :id, 18 | name: "records_temp_entities_record_id_fkey", 19 | type: :uuid, 20 | prefix: "public" 21 | ) 22 | ) 23 | 24 | add( 25 | :temp_entity_id, 26 | references(:temp_entities, 27 | column: :id, 28 | name: "records_temp_entities_temp_entity_id_fkey", 29 | type: :uuid, 30 | prefix: "temp" 31 | ) 32 | ) 33 | end 34 | end 35 | 36 | def down do 37 | drop(constraint(:records_temp_entities, "records_temp_entities_record_id_fkey")) 38 | 39 | drop(constraint(:records_temp_entities, "records_temp_entities_temp_entity_id_fkey")) 40 | 41 | drop(table(:records_temp_entities)) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /priv/test_repo/tenant_migrations/20220805191441_migrate_resources1.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.TenantMigrations.MigrateResources1 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | create table(:multitenant_posts, primary_key: false, prefix: prefix()) do 12 | add :id, :uuid, null: false, default: fragment("uuid_generate_v4()"), primary_key: true 13 | add :name, :text 14 | 15 | add :org_id, 16 | references(:multitenant_orgs, 17 | column: :id, 18 | prefix: "public", 19 | name: "multitenant_posts_org_id_fkey", 20 | type: :uuid 21 | ) 22 | 23 | add :user_id, 24 | references(:users, 25 | column: :id, 26 | prefix: "public", 27 | name: "multitenant_posts_user_id_fkey", 28 | type: :uuid 29 | ) 30 | end 31 | end 32 | 33 | def down do 34 | drop constraint(:multitenant_posts, "multitenant_posts_user_id_fkey") 35 | 36 | drop constraint(:multitenant_posts, "multitenant_posts_org_id_fkey") 37 | 38 | drop table(:multitenant_posts, prefix: prefix()) 39 | end 40 | end -------------------------------------------------------------------------------- /priv/test_repo/tenant_migrations/20240327211149_migrate_resources2.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.TenantMigrations.MigrateResources2 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | alter table(:multitenant_posts, prefix: prefix()) do 12 | modify(:id, :uuid, default: fragment("gen_random_uuid()")) 13 | end 14 | end 15 | 16 | def down do 17 | alter table(:multitenant_posts, prefix: prefix()) do 18 | modify(:id, :uuid, default: fragment("uuid_generate_v4()")) 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /priv/test_repo/tenant_migrations/20240610162043_migrate_resources3.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.TenantMigrations.MigrateResources3 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | create table(:friend_links, primary_key: false, prefix: prefix()) do 12 | add( 13 | :source_id, 14 | references(:multitenant_posts, 15 | column: :id, 16 | name: "friend_links_source_id_fkey", 17 | type: :uuid, 18 | prefix: prefix() 19 | ), 20 | primary_key: true, 21 | null: false 22 | ) 23 | 24 | add( 25 | :dest_id, 26 | references(:multitenant_posts, 27 | column: :id, 28 | name: "friend_links_dest_id_fkey", 29 | type: :uuid, 30 | prefix: prefix() 31 | ), 32 | primary_key: true, 33 | null: false 34 | ) 35 | end 36 | end 37 | 38 | def down do 39 | drop(constraint(:friend_links, "friend_links_source_id_fkey")) 40 | 41 | drop(constraint(:friend_links, "friend_links_dest_id_fkey")) 42 | 43 | drop(table(:friend_links, prefix: prefix())) 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /priv/test_repo/tenant_migrations/20250122203454_migrate_resources4.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.TenantMigrations.MigrateResources4 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | create table(:cross_tenant_post_links, primary_key: false, prefix: prefix()) do 12 | add( 13 | :source_id, 14 | references(:posts, 15 | column: :id, 16 | name: "cross_tenant_post_links_source_id_fkey", 17 | type: :uuid, 18 | prefix: "public" 19 | ), 20 | primary_key: true, 21 | null: false 22 | ) 23 | 24 | add( 25 | :dest_id, 26 | references(:multitenant_posts, 27 | column: :id, 28 | name: "cross_tenant_post_links_dest_id_fkey", 29 | type: :uuid, 30 | prefix: prefix() 31 | ), 32 | primary_key: true, 33 | null: false 34 | ) 35 | end 36 | end 37 | 38 | def down do 39 | drop(constraint(:cross_tenant_post_links, "cross_tenant_post_links_source_id_fkey")) 40 | 41 | drop(constraint(:cross_tenant_post_links, "cross_tenant_post_links_dest_id_fkey")) 42 | 43 | drop(table(:cross_tenant_post_links, prefix: prefix())) 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /priv/test_repo/tenant_migrations/20250220073135_migrate_resources5.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.TenantMigrations.MigrateResources5 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | create table(:composite_key, primary_key: false, prefix: prefix()) do 12 | add(:id, :bigserial, null: false, primary_key: true) 13 | add(:title, :text, null: false) 14 | 15 | add( 16 | :org_id, 17 | references(:multitenant_orgs, 18 | column: :id, 19 | prefix: "public", 20 | name: "composite_key_org_id_fkey", 21 | type: :uuid 22 | ) 23 | ) 24 | end 25 | end 26 | 27 | def down do 28 | drop(constraint(:composite_key, "composite_key_org_id_fkey")) 29 | 30 | drop(table(:composite_key, prefix: prefix())) 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /priv/test_repo/tenant_migrations/20250220073141_migrate_resources6.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo.TenantMigrations.MigrateResources6 do 2 | @moduledoc """ 3 | Updates resources based on their most recent snapshots. 4 | 5 | This file was autogenerated with `mix ash_postgres.generate_migrations` 6 | """ 7 | 8 | use Ecto.Migration 9 | 10 | def up do 11 | drop(constraint("composite_key", "composite_key_pkey", prefix: prefix())) 12 | 13 | alter table(:composite_key, prefix: prefix()) do 14 | modify(:title, :text) 15 | end 16 | 17 | execute("ALTER TABLE \"#{prefix()}\".\"composite_key\" ADD PRIMARY KEY (id, title)") 18 | end 19 | 20 | def down do 21 | drop(constraint("composite_key", "composite_key_pkey", prefix: prefix())) 22 | 23 | alter table(:composite_key, prefix: prefix()) do 24 | modify(:title, :text) 25 | end 26 | 27 | execute("ALTER TABLE \"#{prefix()}\".\"composite_key\" ADD PRIMARY KEY (id)") 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /test/cascade_destroy_test.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgresTest.CascadeDestroyTest do 2 | use AshPostgres.RepoCase, async: true 3 | 4 | alias AshPostgres.Test.{Post, Rating} 5 | 6 | test "can cascade destroy a has_many with parent filter" do 7 | post = 8 | Post.create!("post", %{score: 1}) 9 | 10 | Rating 11 | |> Ash.Changeset.for_create(:create, %{score: 2, resource_id: post.id}) 12 | |> Ash.Changeset.set_context(%{data_layer: %{table: "post_ratings"}}) 13 | |> Ash.create!() 14 | 15 | post 16 | |> Ash.Changeset.for_destroy(:cascade_destroy) 17 | |> Ash.destroy!() 18 | 19 | assert [] = 20 | Rating 21 | |> Ash.Query.for_read(:read) 22 | |> Ash.Query.set_context(%{data_layer: %{table: "post_ratings"}}) 23 | |> Ash.read!() 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/composite_type_test.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.CompositeTypeTest do 2 | use AshPostgres.RepoCase 3 | alias AshPostgres.Test.Post 4 | require Ash.Query 5 | 6 | test "can be cast and stored" do 7 | post = 8 | Post 9 | |> Ash.Changeset.for_create(:create, %{title: "locked", composite_point: %{x: 1, y: 2}}) 10 | |> Ash.create!() 11 | 12 | assert post.composite_point.x == 1 13 | assert post.composite_point.y == 2 14 | end 15 | 16 | test "can be referenced in expressions" do 17 | post = 18 | Post 19 | |> Ash.Changeset.for_create(:create, %{title: "locked", composite_point: %{x: 1, y: 2}}) 20 | |> Ash.create!() 21 | 22 | post_id = post.id 23 | 24 | assert %{id: ^post_id} = Post |> Ash.Query.filter(composite_point[:x] == 1) |> Ash.read_one!() 25 | refute Post |> Ash.Query.filter(composite_point[:x] == 2) |> Ash.read_one!() 26 | end 27 | 28 | test "composite types can be constructed" do 29 | Post 30 | |> Ash.Changeset.for_create(:create, %{title: "locked", composite_point: %{x: 1, y: 2}}) 31 | |> Ash.create!() 32 | 33 | assert %{composite_origin: %{x: 0, y: 0}} = 34 | Post 35 | |> Ash.Query.load(:composite_origin) 36 | |> Ash.read_one!() 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /test/constraint_test.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.ConstraintTest do 2 | @moduledoc false 3 | use AshPostgres.RepoCase, async: false 4 | alias AshPostgres.Test.Post 5 | 6 | require Ash.Query 7 | 8 | test "constraint messages are properly raised" do 9 | assert_raise Ash.Error.Invalid, ~r/yo, bad price/, fn -> 10 | Post 11 | |> Ash.Changeset.for_create(:create, %{title: "title", price: -1}) 12 | |> Ash.create!() 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/custom_expression_test.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.CustomExpressionTest do 2 | use AshPostgres.RepoCase, async: false 3 | 4 | test "unique constraint errors are properly caught" do 5 | Ash.create!(AshPostgres.Test.Profile, %{description: "foo"}) 6 | 7 | assert [_] = 8 | AshPostgres.Test.Profile 9 | |> Ash.Query.for_read(:by_indirectly_matching_description, %{term: "fop"}) 10 | |> Ash.read!() 11 | 12 | assert [_] = 13 | AshPostgres.Test.Profile 14 | |> Ash.Query.for_read(:by_directly_matching_description, %{term: "fop"}) 15 | |> Ash.read!() 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/custom_index_test.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.CustomIndexTest do 2 | use AshPostgres.RepoCase, async: false 3 | alias AshPostgres.Test.Post 4 | 5 | require Ash.Query 6 | 7 | test "unique constraint errors are properly caught" do 8 | Post 9 | |> Ash.Changeset.for_create(:create, %{ 10 | title: "first", 11 | uniq_custom_one: "what", 12 | uniq_custom_two: "what2" 13 | }) 14 | |> Ash.create!() 15 | 16 | assert_raise Ash.Error.Invalid, 17 | ~r/Invalid value provided for uniq_custom_one: dude what the heck/, 18 | fn -> 19 | Post 20 | |> Ash.Changeset.for_create(:create, %{ 21 | title: "first", 22 | uniq_custom_one: "what", 23 | uniq_custom_two: "what2" 24 | }) 25 | |> Ash.create!() 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /test/cve/empty_atomic_non_bulk_actions_policy_bypass_test.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.EmptyAtomicNonBulkActionsPolicyBypassTest do 2 | @moduledoc """ 3 | This is test verifies the fix for the following CVE: 4 | 5 | https://github.com/ash-project/ash_postgres/security/advisories/GHSA-hf59-7rwq-785m 6 | """ 7 | use AshPostgres.RepoCase, async: false 8 | 9 | alias AshPostgres.Test.PostWithEmptyUpdate 10 | 11 | require Ash.Query 12 | 13 | test "a forbidden error is appropriately raised on atomic upgraded, empty, non-bulk actions" do 14 | post = 15 | PostWithEmptyUpdate 16 | |> Ash.Changeset.for_create(:create, %{}) 17 | |> Ash.create!() 18 | 19 | assert_raise Ash.Error.Forbidden, fn -> 20 | post 21 | |> Ash.Changeset.for_update(:empty_update, %{}, authorize?: true) 22 | |> Ash.update!() 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/ecto_compatibility_test.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.EctoCompatibilityTest do 2 | use AshPostgres.RepoCase, async: false 3 | require Ash.Query 4 | 5 | test "call Ecto.Repo.insert! via Ash Repo" do 6 | org = 7 | %AshPostgres.Test.Organization{name: "The Org"} 8 | |> AshPostgres.TestRepo.insert!() 9 | 10 | assert org.name == "The Org" 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/embeddable_resource_test.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.EmbeddableResourceTest do 2 | @moduledoc false 3 | use AshPostgres.RepoCase, async: false 4 | alias AshPostgres.Test.{Author, Bio, Post} 5 | 6 | require Ash.Query 7 | 8 | setup do 9 | post = 10 | Post 11 | |> Ash.Changeset.for_create(:create, %{title: "title"}) 12 | |> Ash.create!() 13 | 14 | %{post: post} 15 | end 16 | 17 | test "calculations can load json", %{post: post} do 18 | assert %{calc_returning_json: %AshPostgres.Test.Money{amount: 100, currency: :usd}} = 19 | Ash.load!(post, :calc_returning_json) 20 | end 21 | 22 | test "embeds with list attributes set to nil are loaded as nil" do 23 | author = 24 | Author 25 | |> Ash.Changeset.for_create(:create, %{bio: %Bio{list_of_strings: nil}}) 26 | |> Ash.create!() 27 | 28 | assert is_nil(author.bio.list_of_strings) 29 | 30 | author = Ash.reload!(author) 31 | 32 | assert is_nil(author.bio.list_of_strings) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /test/enum_test.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.EnumTest do 2 | @moduledoc false 3 | use AshPostgres.RepoCase, async: false 4 | alias AshPostgres.Test.Post 5 | 6 | require Ash.Query 7 | 8 | test "valid values are properly inserted" do 9 | Post 10 | |> Ash.Changeset.for_create(:create, %{title: "title", status: :open}) 11 | |> Ash.create!() 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/filter_child_relationship_by_parent_relationship_test.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.Support.Relationships.FilterChileRelationshipByParentRelationshipTest do 2 | use AshPostgres.RepoCase, async: false 3 | alias AshPostgres.Test.{Comment, Post} 4 | 5 | require Ash.Query 6 | 7 | describe "loading ratings of a comment filtered by a post" do 8 | setup do 9 | post = 10 | Post 11 | |> Ash.Changeset.for_create(:create, %{title: "Post Title", score: 2}) 12 | |> Ash.create!() 13 | 14 | ratings = 15 | for i <- [1, 2, 2, 2, 3, 4, 5] do 16 | %{score: i} 17 | end 18 | 19 | comment = 20 | Comment 21 | |> Ash.Changeset.for_create(:create, %{title: "Comment Title"}) 22 | |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) 23 | |> Ash.Changeset.manage_relationship(:ratings, ratings, type: :create) 24 | |> Ash.create!() 25 | 26 | [post: post, comment: comment] 27 | end 28 | 29 | test "it can load the ratings_with_same_score_as_post relationship", %{ 30 | comment: comment 31 | } do 32 | comment = Ash.load!(comment, :ratings_with_same_score_as_post) 33 | 34 | ratings = comment.ratings_with_same_score_as_post 35 | 36 | assert Enum.count(ratings) == 3 37 | assert Enum.all?(ratings, &(&1.score == 2)) 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /test/filter_field_policy_test.exs: -------------------------------------------------------------------------------- 1 | defmodule FilterFieldPolicyTest do 2 | use AshPostgres.RepoCase, async: false 3 | 4 | alias AshPostgres.Test.{Organization, Post, User} 5 | 6 | require Ash.Query 7 | 8 | test "filter uses the correct field policies when exanding refs" do 9 | organization = 10 | Organization 11 | |> Ash.Changeset.for_create(:create, %{name: "test_org"}) 12 | |> Ash.create!() 13 | 14 | User 15 | |> Ash.Changeset.for_create(:create, %{organization_id: organization.id, name: "foo bar"}) 16 | |> Ash.create!() 17 | 18 | Post 19 | |> Ash.Changeset.for_create(:create, %{organization_id: organization.id}) 20 | |> Ash.create!() 21 | 22 | filter = Ash.Filter.parse_input!(Post, %{organization: %{name: %{ilike: "%org"}}}) 23 | 24 | assert [_] = 25 | Post 26 | |> Ash.Query.do_filter(filter) 27 | |> Ash.Query.for_read(:allow_any) 28 | |> Ash.read!(actor: %{id: "test"}) 29 | 30 | filter = Ash.Filter.parse_input!(Post, %{organization: %{users: %{name: %{ilike: "%bar"}}}}) 31 | 32 | assert [_] = 33 | Post 34 | |> Ash.Query.do_filter(filter) 35 | |> Ash.Query.for_read(:allow_any) 36 | |> Ash.read!(actor: %{id: "test"}) 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /test/manual_update_test.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.ManualUpdateTest do 2 | use AshPostgres.RepoCase, async: true 3 | 4 | test "Manual update defined in a module to update an attribute" do 5 | post = 6 | AshPostgres.Test.Post 7 | |> Ash.Changeset.for_create(:create, %{title: "match"}) 8 | |> Ash.create!() 9 | 10 | AshPostgres.Test.Comment 11 | |> Ash.Changeset.for_create(:create, %{title: "_"}) 12 | |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) 13 | |> Ash.create!() 14 | 15 | post = 16 | post 17 | |> Ash.Changeset.for_update(:manual_update) 18 | |> Ash.update!() 19 | 20 | assert post.title == "manual" 21 | 22 | # The manual update has a call to Ash.Changeset.load that should 23 | # cause the comments to be loaded 24 | assert Ash.Resource.loaded?(post, :comments) 25 | assert Enum.count(post.comments) == 1 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/mix/tasks/ash_postgres.install_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.AshPostgres.InstallTest do 2 | use ExUnit.Case 3 | 4 | import Igniter.Test 5 | 6 | # This is a simple test to ensure that the installation doesnt have 7 | # any errors. We should add better tests here, though. 8 | test "installation does not fail" do 9 | test_project() 10 | |> Igniter.compose_task("ash_postgres.install", ["--yes"]) 11 | |> assert_creates("lib/test/repo.ex") 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/polymorphism_test.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.PolymorphismTest do 2 | use AshPostgres.RepoCase, async: false 3 | alias AshPostgres.Test.{Post, Rating} 4 | 5 | require Ash.Query 6 | 7 | test "you can create related data" do 8 | Post 9 | |> Ash.Changeset.for_create(:create, rating: %{score: 10}) 10 | |> Ash.create!() 11 | 12 | assert [%{score: 10}] = 13 | Rating 14 | |> Ash.Query.set_context(%{data_layer: %{table: "post_ratings"}}) 15 | |> Ash.read!() 16 | end 17 | 18 | test "you can read related data" do 19 | Post 20 | |> Ash.Changeset.for_create(:create, rating: %{score: 10}) 21 | |> Ash.create!() 22 | 23 | assert [%{score: 10}] = 24 | Post 25 | |> Ash.Query.load(:ratings) 26 | |> Ash.read_one!() 27 | |> Map.get(:ratings) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /test/select_test.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.SelectTest do 2 | @moduledoc false 3 | use AshPostgres.RepoCase, async: false 4 | alias AshPostgres.Test.Post 5 | 6 | require Ash.Query 7 | 8 | test "values not selected in the query are not present in the response" do 9 | Post 10 | |> Ash.Changeset.for_create(:create, %{title: "title"}) 11 | |> Ash.create!() 12 | 13 | assert [%{title: %Ash.NotLoaded{}}] = Ash.read!(Ash.Query.select(Post, :id)) 14 | end 15 | 16 | test "values not selected in a changeset are not present in the response" do 17 | assert %{title: %Ash.NotLoaded{}} = 18 | Post 19 | |> Ash.Changeset.for_create(:create, %{title: "title"}) 20 | |> Ash.Changeset.select([]) 21 | |> Ash.create!() 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /test/storage_types_test.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.StorageTypesTest do 2 | use AshPostgres.RepoCase, async: false 3 | 4 | alias Ash.BulkResult 5 | alias AshPostgres.Test.Author 6 | 7 | require Ash.Query 8 | 9 | test "can save {:array, :map} as jsonb" do 10 | %{id: id} = 11 | Author 12 | |> Ash.Changeset.for_create( 13 | :create, 14 | %{bios: [%{title: "bio1"}, %{title: "bio2"}]} 15 | ) 16 | |> Ash.create!() 17 | 18 | # testing empty list edge case 19 | %BulkResult{records: [author]} = 20 | Author 21 | |> Ash.Query.filter(id == ^id) 22 | |> Ash.bulk_update(:update, %{bios: []}, 23 | return_errors?: true, 24 | notify?: true, 25 | strategy: [:atomic, :stream, :atomic_batches], 26 | allow_stream_with: :full_read, 27 | return_records?: true 28 | ) 29 | 30 | assert author.bios == [] 31 | 32 | %BulkResult{records: [author]} = 33 | Author 34 | |> Ash.Query.filter(id == ^id) 35 | |> Ash.bulk_update(:update, %{bios: [%{a: 1}]}, 36 | return_errors?: true, 37 | notify?: true, 38 | strategy: [:atomic, :stream, :atomic_batches], 39 | allow_stream_with: :full_read, 40 | return_records?: true 41 | ) 42 | 43 | assert author.bios == [%{"a" => 1}] 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /test/subquery_test.exs: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.SubqueryTest do 2 | use AshPostgres.RepoCase, async: false 3 | 4 | alias AshPostgres.Test.Subquery.{Access, Child, Parent, Through} 5 | 6 | test "joins are correctly wrapped in subqueries" do 7 | {:ok, child} = Child.create(%{}) 8 | 9 | {:ok, parent} = 10 | Parent.create(%{visible: true}) 11 | 12 | Access.create(%{parent_id: parent.id, email: "foo@bar.com"}) 13 | 14 | Through.create(%{parent_id: parent.id, child_id: child.id}) 15 | 16 | Child.read!(actor: %{email: "foo@bar.com"}) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /test/support/complex_calculations/domain.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.ComplexCalculations.Domain do 2 | @moduledoc false 3 | use Ash.Domain 4 | 5 | resources do 6 | resource(AshPostgres.Test.ComplexCalculations.Certification) 7 | resource(AshPostgres.Test.ComplexCalculations.Skill) 8 | resource(AshPostgres.Test.ComplexCalculations.Documentation) 9 | resource(AshPostgres.Test.ComplexCalculations.Channel) 10 | resource(AshPostgres.Test.ComplexCalculations.DMChannel) 11 | resource(AshPostgres.Test.ComplexCalculations.ChannelMember) 12 | end 13 | 14 | authorization do 15 | authorize(:when_requested) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/support/complex_calculations/resources/channel_member.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.ComplexCalculations.ChannelMember do 2 | @moduledoc false 3 | use Ash.Resource, 4 | domain: AshPostgres.Test.ComplexCalculations.Domain, 5 | data_layer: AshPostgres.DataLayer, 6 | authorizers: [Ash.Policy.Authorizer] 7 | 8 | actions do 9 | default_accept(:*) 10 | 11 | defaults([:create, :read, :update, :destroy]) 12 | end 13 | 14 | attributes do 15 | uuid_primary_key(:id) 16 | 17 | create_timestamp(:created_at, public?: true) 18 | update_timestamp(:updated_at, public?: true) 19 | end 20 | 21 | calculations do 22 | calculate( 23 | :first_member_recently_created, 24 | :boolean, 25 | expr(channel.first_member.created_at > ago(1, :day)) 26 | ) 27 | end 28 | 29 | postgres do 30 | table "complex_calculations_certifications_channel_members" 31 | repo(AshPostgres.TestRepo) 32 | end 33 | 34 | relationships do 35 | belongs_to(:user, AshPostgres.Test.User, domain: AshPostgres.Test.Domain, public?: true) 36 | 37 | belongs_to(:channel, AshPostgres.Test.ComplexCalculations.Channel, public?: true) 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /test/support/concat.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.Concat do 2 | @moduledoc false 3 | use Ash.Resource.Calculation 4 | require Ash.Query 5 | 6 | def init(opts) do 7 | if opts[:keys] && is_list(opts[:keys]) && Enum.all?(opts[:keys], &is_atom/1) do 8 | {:ok, opts} 9 | else 10 | {:error, "Expected a `keys` option for which keys to concat"} 11 | end 12 | end 13 | 14 | def expression(opts, %{arguments: %{separator: separator}}) do 15 | Enum.reduce(opts[:keys], nil, fn key, expr -> 16 | if expr do 17 | if separator do 18 | expr(^expr <> ^separator <> ^ref(key)) 19 | else 20 | expr(^expr <> ^ref(key)) 21 | end 22 | else 23 | expr(^ref(key)) 24 | end 25 | end) 26 | end 27 | 28 | def calculate(records, opts, %{separator: separator}) do 29 | Enum.map(records, fn record -> 30 | Enum.map_join(opts[:keys], separator, fn key -> 31 | to_string(Map.get(record, key)) 32 | end) 33 | end) 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /test/support/dev_test_repo.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.DevTestRepo do 2 | @moduledoc false 3 | use AshPostgres.Repo, 4 | otp_app: :ash_postgres 5 | 6 | def on_transaction_begin(data) do 7 | send(self(), data) 8 | end 9 | 10 | def prefer_transaction?, do: false 11 | 12 | def prefer_transaction_for_atomic_updates?, do: false 13 | 14 | def installed_extensions do 15 | ["ash-functions", "uuid-ossp", "pg_trgm", "citext", AshPostgres.TestCustomExtension, "ltree"] -- 16 | Application.get_env(:ash_postgres, :no_extensions, []) 17 | end 18 | 19 | def min_pg_version do 20 | case System.get_env("PG_VERSION") do 21 | nil -> 22 | %Version{major: 16, minor: 0, patch: 0} 23 | 24 | version -> 25 | case Integer.parse(version) do 26 | {major, ""} -> %Version{major: major, minor: 0, patch: 0} 27 | _ -> Version.parse!(version) 28 | end 29 | end 30 | end 31 | 32 | def all_tenants do 33 | Code.ensure_compiled(AshPostgres.MultitenancyTest.Org) 34 | 35 | AshPostgres.MultitenancyTest.DevMigrationsOrg 36 | |> Ash.read!() 37 | |> Enum.map(&"org_#{&1.id}") 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /test/support/multi_domain_calculations/domain_one.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.MultiDomainCalculations.DomainOne do 2 | @moduledoc false 3 | use Ash.Domain 4 | 5 | resources do 6 | resource(AshPostgres.Test.MultiDomainCalculations.DomainOne.Item) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/support/multi_domain_calculations/domain_one/item.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.MultiDomainCalculations.DomainOne.Item do 2 | @moduledoc false 3 | 4 | use Ash.Resource, 5 | data_layer: AshPostgres.DataLayer, 6 | authorizers: [Ash.Policy.Authorizer], 7 | domain: AshPostgres.Test.MultiDomainCalculations.DomainOne 8 | 9 | alias AshPostgres.Test.MultiDomainCalculations.DomainThree.RelationshipItem 10 | alias AshPostgres.Test.MultiDomainCalculations.DomainTwo.OtherItem 11 | 12 | attributes do 13 | uuid_v7_primary_key(:id) 14 | attribute(:key, :string) 15 | create_timestamp(:inserted_at) 16 | update_timestamp(:updated_at) 17 | end 18 | 19 | relationships do 20 | has_one(:other_item, OtherItem) 21 | 22 | has_one(:relationship_item, RelationshipItem) do 23 | no_attributes?(true) 24 | filter(expr(parent(key) == key)) 25 | end 26 | end 27 | 28 | actions do 29 | defaults([:read, :destroy, update: :*, create: [:*, :key]]) 30 | end 31 | 32 | calculations do 33 | calculate(:total_amount, :integer, expr(other_item.total_amount)) 34 | calculate(:total_amount_relationship, :integer, expr(other_item.total_amount_relationship)) 35 | end 36 | 37 | policies do 38 | policy always() do 39 | authorize_if(always()) 40 | end 41 | end 42 | 43 | postgres do 44 | table "items" 45 | repo(AshPostgres.TestRepo) 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /test/support/multi_domain_calculations/domain_three.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.MultiDomainCalculations.DomainThree do 2 | @moduledoc false 3 | use Ash.Domain 4 | 5 | resources do 6 | resource(AshPostgres.Test.MultiDomainCalculations.DomainThree.RelationshipItem) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/support/multi_domain_calculations/domain_three/relationship_item.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.MultiDomainCalculations.DomainThree.RelationshipItem do 2 | @moduledoc false 3 | 4 | use Ash.Resource, 5 | data_layer: AshPostgres.DataLayer, 6 | authorizers: [Ash.Policy.Authorizer], 7 | domain: AshPostgres.Test.MultiDomainCalculations.DomainThree 8 | 9 | attributes do 10 | uuid_v7_primary_key(:id) 11 | attribute(:key, :string, allow_nil?: false) 12 | attribute(:value, :integer, allow_nil?: false) 13 | create_timestamp(:inserted_at) 14 | update_timestamp(:updated_at) 15 | end 16 | 17 | actions do 18 | defaults([:read, :destroy, update: :*, create: [:*, :key, :value]]) 19 | end 20 | 21 | policies do 22 | policy always() do 23 | authorize_if(always()) 24 | end 25 | end 26 | 27 | postgres do 28 | table "relationship_items" 29 | repo(AshPostgres.TestRepo) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /test/support/multi_domain_calculations/domain_two.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.MultiDomainCalculations.DomainTwo do 2 | @moduledoc false 3 | use Ash.Domain 4 | 5 | resources do 6 | resource(AshPostgres.Test.MultiDomainCalculations.DomainTwo.OtherItem) 7 | resource(AshPostgres.Test.MultiDomainCalculations.DomainTwo.SubItem) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/support/multi_domain_calculations/domain_two/sub_item.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.MultiDomainCalculations.DomainTwo.SubItem do 2 | @moduledoc false 3 | 4 | use Ash.Resource, 5 | data_layer: AshPostgres.DataLayer, 6 | authorizers: [Ash.Policy.Authorizer], 7 | domain: AshPostgres.Test.MultiDomainCalculations.DomainTwo 8 | 9 | alias AshPostgres.Test.MultiDomainCalculations.DomainTwo.OtherItem 10 | 11 | attributes do 12 | uuid_v7_primary_key(:id) 13 | attribute(:amount, :integer, allow_nil?: false) 14 | create_timestamp(:inserted_at) 15 | update_timestamp(:updated_at) 16 | end 17 | 18 | relationships do 19 | belongs_to(:other_item, OtherItem, allow_nil?: false) 20 | end 21 | 22 | actions do 23 | defaults([:read, :destroy, create: [:*, :other_item_id, :amount], update: :*]) 24 | end 25 | 26 | calculations do 27 | calculate(:total_amount, :integer, expr(amount)) 28 | 29 | calculate( 30 | :total_amount_relationship, 31 | :integer, 32 | expr(amount * other_item.item.relationship_item.value) 33 | ) 34 | end 35 | 36 | policies do 37 | policy always() do 38 | authorize_if(always()) 39 | end 40 | end 41 | 42 | postgres do 43 | table "sub_items" 44 | repo(AshPostgres.TestRepo) 45 | 46 | references do 47 | reference :other_item, on_delete: :delete, index?: true 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /test/support/multitenancy/domain.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.MultitenancyTest.Domain do 2 | @moduledoc false 3 | use Ash.Domain 4 | 5 | resources do 6 | resource(AshPostgres.MultitenancyTest.Org) 7 | resource(AshPostgres.MultitenancyTest.DevMigrationsOrg) 8 | resource(AshPostgres.MultitenancyTest.NamedOrg) 9 | resource(AshPostgres.MultitenancyTest.User) 10 | resource(AshPostgres.MultitenancyTest.Post) 11 | resource(AshPostgres.MultitenancyTest.PostLink) 12 | resource(AshPostgres.MultitenancyTest.NonMultitenantPostLink) 13 | resource(AshPostgres.MultitenancyTest.CrossTenantPostLink) 14 | resource(AshPostgres.MultitenancyTest.CompositeKeyPost) 15 | end 16 | 17 | authorization do 18 | authorize(:when_requested) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /test/support/multitenancy/resources/composite_key_post.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.MultitenancyTest.CompositeKeyPost do 2 | @moduledoc false 3 | use Ash.Resource, 4 | domain: AshPostgres.MultitenancyTest.Domain, 5 | data_layer: AshPostgres.DataLayer 6 | 7 | postgres do 8 | table "composite_key" 9 | repo AshPostgres.TestRepo 10 | end 11 | 12 | multitenancy do 13 | strategy(:context) 14 | end 15 | 16 | actions do 17 | default_accept(:*) 18 | 19 | defaults([:create, :read, :update, :destroy]) 20 | end 21 | 22 | attributes do 23 | integer_primary_key(:id) 24 | attribute(:title, :string, public?: true, allow_nil?: false, primary_key?: true) 25 | end 26 | 27 | relationships do 28 | belongs_to(:org, AshPostgres.MultitenancyTest.Org) do 29 | public?(true) 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /test/support/multitenancy/resources/cross_tenant_post_link.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.MultitenancyTest.CrossTenantPostLink do 2 | @moduledoc false 3 | use Ash.Resource, 4 | domain: AshPostgres.MultitenancyTest.Domain, 5 | data_layer: AshPostgres.DataLayer 6 | 7 | postgres do 8 | table "cross_tenant_post_links" 9 | repo AshPostgres.TestRepo 10 | end 11 | 12 | multitenancy do 13 | strategy(:context) 14 | end 15 | 16 | actions do 17 | defaults([:read, :destroy, create: :*, update: :*]) 18 | end 19 | 20 | relationships do 21 | belongs_to(:source, AshPostgres.Test.Post, 22 | primary_key?: true, 23 | allow_nil?: false 24 | ) 25 | 26 | belongs_to(:dest, AshPostgres.MultitenancyTest.Post, 27 | primary_key?: true, 28 | allow_nil?: false 29 | ) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /test/support/multitenancy/resources/named_org.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.MultitenancyTest.NamedOrg do 2 | @moduledoc false 3 | use Ash.Resource, 4 | domain: AshPostgres.MultitenancyTest.Domain, 5 | data_layer: AshPostgres.DataLayer 6 | 7 | defimpl Ash.ToTenant do 8 | def to_tenant(%{name: name}, resource) do 9 | if Ash.Resource.Info.data_layer(resource) == AshPostgres.DataLayer && 10 | Ash.Resource.Info.multitenancy_strategy(resource) == :context do 11 | "org_#{name}" 12 | else 13 | name 14 | end 15 | end 16 | end 17 | 18 | attributes do 19 | attribute(:name, :string, 20 | primary_key?: true, 21 | allow_nil?: false, 22 | public?: true, 23 | writable?: true 24 | ) 25 | end 26 | 27 | actions do 28 | default_accept(:*) 29 | 30 | defaults([:create, :read, :update, :destroy]) 31 | end 32 | 33 | postgres do 34 | table "multitenant_named_orgs" 35 | repo(AshPostgres.TestRepo) 36 | 37 | manage_tenant do 38 | template(["org_", :name]) 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /test/support/multitenancy/resources/non_multitenant_post_link.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.MultitenancyTest.NonMultitenantPostLink do 2 | @moduledoc false 3 | use Ash.Resource, 4 | domain: AshPostgres.MultitenancyTest.Domain, 5 | data_layer: AshPostgres.DataLayer 6 | 7 | postgres do 8 | table "non_multitenant_post_links" 9 | repo AshPostgres.TestRepo 10 | end 11 | 12 | actions do 13 | default_accept(:*) 14 | 15 | defaults([:create, :read, :update, :destroy]) 16 | end 17 | 18 | identities do 19 | identity(:unique_link, [:source_id, :dest_id]) 20 | end 21 | 22 | attributes do 23 | attribute :state, :atom do 24 | public?(true) 25 | constraints(one_of: [:active, :archived]) 26 | default(:active) 27 | end 28 | end 29 | 30 | relationships do 31 | belongs_to :source, AshPostgres.MultitenancyTest.Post do 32 | public?(true) 33 | allow_nil?(false) 34 | primary_key?(true) 35 | end 36 | 37 | belongs_to :dest, AshPostgres.Test.Post do 38 | public?(true) 39 | allow_nil?(false) 40 | primary_key?(true) 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /test/support/multitenancy/resources/post_link.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.MultitenancyTest.PostLink do 2 | @moduledoc false 3 | use Ash.Resource, 4 | domain: AshPostgres.MultitenancyTest.Domain, 5 | data_layer: AshPostgres.DataLayer 6 | 7 | postgres do 8 | table "friend_links" 9 | repo AshPostgres.TestRepo 10 | end 11 | 12 | multitenancy do 13 | strategy(:context) 14 | end 15 | 16 | actions do 17 | defaults([:read, :destroy, create: :*, update: :*]) 18 | end 19 | 20 | relationships do 21 | belongs_to(:source, AshPostgres.MultitenancyTest.Post, 22 | primary_key?: true, 23 | allow_nil?: false 24 | ) 25 | 26 | belongs_to(:dest, AshPostgres.MultitenancyTest.Post, 27 | primary_key?: true, 28 | allow_nil?: false 29 | ) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /test/support/multitenancy/resources/user.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.MultitenancyTest.User do 2 | @moduledoc false 3 | use Ash.Resource, 4 | domain: AshPostgres.MultitenancyTest.Domain, 5 | data_layer: AshPostgres.DataLayer 6 | 7 | attributes do 8 | uuid_primary_key(:id, writable?: true) 9 | attribute(:name, :string, public?: true) 10 | attribute(:org_id, :uuid, public?: true) 11 | end 12 | 13 | postgres do 14 | table "users" 15 | repo AshPostgres.TestRepo 16 | end 17 | 18 | actions do 19 | default_accept(:*) 20 | 21 | defaults([:create, :read, :update, :destroy]) 22 | end 23 | 24 | multitenancy do 25 | # Tells the resource to use the data layer 26 | # multitenancy, in this case separate postgres schemas 27 | strategy(:attribute) 28 | attribute(:org_id) 29 | parse_attribute({__MODULE__, :parse_tenant, []}) 30 | global?(true) 31 | end 32 | 33 | relationships do 34 | belongs_to(:org, AshPostgres.MultitenancyTest.Org) do 35 | public?(true) 36 | end 37 | 38 | has_many :posts, AshPostgres.MultitenancyTest.Post do 39 | public?(true) 40 | end 41 | end 42 | 43 | aggregates do 44 | list(:years_visited, :posts, :last_word) 45 | count(:count_visited, :posts) 46 | end 47 | 48 | def parse_tenant("org_" <> id), do: id 49 | end 50 | -------------------------------------------------------------------------------- /test/support/repo_case.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.RepoCase do 2 | @moduledoc false 3 | use ExUnit.CaseTemplate 4 | 5 | alias Ecto.Adapters.SQL.Sandbox 6 | 7 | using do 8 | quote do 9 | alias AshPostgres.TestRepo 10 | 11 | import Ecto 12 | import Ecto.Query 13 | import AshPostgres.RepoCase 14 | 15 | # and any other stuff 16 | end 17 | end 18 | 19 | setup tags do 20 | :ok = Sandbox.checkout(AshPostgres.TestRepo) 21 | 22 | if !tags[:async] do 23 | Sandbox.mode(AshPostgres.TestRepo, {:shared, self()}) 24 | end 25 | 26 | :ok 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /test/support/resources/account.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.Account do 2 | @moduledoc false 3 | use Ash.Resource, 4 | domain: AshPostgres.Test.Domain, 5 | data_layer: AshPostgres.DataLayer 6 | 7 | actions do 8 | default_accept(:*) 9 | 10 | defaults([:create, :read, :update, :destroy]) 11 | end 12 | 13 | aggregates do 14 | first(:user_is_active, :user, :is_active) 15 | end 16 | 17 | attributes do 18 | uuid_primary_key(:id) 19 | attribute(:is_active, :boolean, public?: true) 20 | end 21 | 22 | calculations do 23 | calculate( 24 | :active, 25 | :boolean, 26 | expr(is_active && user_is_active), 27 | load: [:user_is_active] 28 | ) 29 | end 30 | 31 | postgres do 32 | table "accounts" 33 | repo(AshPostgres.TestRepo) 34 | end 35 | 36 | relationships do 37 | belongs_to(:user, AshPostgres.Test.User) do 38 | public?(true) 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /test/support/resources/bio.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.Bio do 2 | @moduledoc false 3 | use Ash.Resource, data_layer: :embedded 4 | 5 | actions do 6 | default_accept(:*) 7 | 8 | defaults([:create, :read, :update, :destroy]) 9 | end 10 | 11 | attributes do 12 | attribute(:title, :string, public?: true) 13 | attribute(:bio, :string, public?: true) 14 | attribute(:years_of_experience, :integer, public?: true) 15 | 16 | attribute :list_of_strings, {:array, :string} do 17 | public?(true) 18 | allow_nil?(true) 19 | default(nil) 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /test/support/resources/co_authored_post.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.CoAuthorPost do 2 | @moduledoc false 3 | use Ash.Resource, 4 | domain: AshPostgres.Test.Domain, 5 | data_layer: AshPostgres.DataLayer 6 | 7 | postgres do 8 | table "co_authored_posts" 9 | repo AshPostgres.TestRepo 10 | end 11 | 12 | attributes do 13 | attribute :role, :atom do 14 | allow_nil?(false) 15 | public?(true) 16 | 17 | constraints(one_of: [:editor, :writer, :proof_reader]) 18 | end 19 | 20 | attribute :was_cancelled_at, :datetime do 21 | allow_nil?(true) 22 | public?(true) 23 | end 24 | end 25 | 26 | actions do 27 | default_accept(:*) 28 | 29 | defaults([:read, :update, :destroy]) 30 | 31 | create :create do 32 | end 33 | 34 | update :cancel_author do 35 | change(set_attribute(:was_cancelled_at, DateTime.utc_now())) 36 | end 37 | 38 | update :uncancel_author do 39 | change(set_attribute(:was_cancelled_at, nil)) 40 | end 41 | end 42 | 43 | code_interface do 44 | define(:cancel, action: :cancel_author) 45 | define(:uncancel, action: :uncancel_author) 46 | end 47 | 48 | relationships do 49 | belongs_to :author, AshPostgres.Test.Author do 50 | primary_key?(true) 51 | public?(true) 52 | allow_nil?(false) 53 | end 54 | 55 | belongs_to :post, AshPostgres.Test.Post do 56 | primary_key?(true) 57 | public?(true) 58 | allow_nil?(false) 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /test/support/resources/comedian.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.Comedian do 2 | @moduledoc false 3 | use Ash.Resource, 4 | domain: AshPostgres.Test.Domain, 5 | data_layer: AshPostgres.DataLayer, 6 | authorizers: [Ash.Policy.Authorizer] 7 | 8 | attributes do 9 | uuid_primary_key(:id) 10 | attribute(:name, :string, public?: true) 11 | create_timestamp(:inserted_at, public?: true) 12 | update_timestamp(:updated_at, public?: true) 13 | end 14 | 15 | relationships do 16 | belongs_to(:standup_club, AshPostgres.Test.StandupClub, public?: true) 17 | has_many(:jokes, AshPostgres.Test.Joke, public?: true) 18 | end 19 | 20 | calculations do 21 | calculate(:has_jokes_mod, :boolean, AshPostgres.Test.Comedian.HasJokes) 22 | calculate(:has_jokes_expr, :boolean, expr(has_jokes_mod == true)) 23 | end 24 | 25 | aggregates do 26 | count(:punchline_count, [:jokes, :punchlines], public?: true) 27 | end 28 | 29 | actions do 30 | defaults([:read]) 31 | 32 | create :create do 33 | primary?(true) 34 | accept([:name]) 35 | end 36 | end 37 | 38 | code_interface do 39 | define(:create) 40 | end 41 | 42 | postgres do 43 | table("comedians") 44 | repo(AshPostgres.TestRepo) 45 | end 46 | end 47 | 48 | defmodule AshPostgres.Test.Comedian.HasJokes do 49 | @moduledoc false 50 | use Ash.Resource.Calculation 51 | 52 | @impl true 53 | def load(_, _, _) do 54 | [:jokes] 55 | end 56 | 57 | @impl true 58 | def calculate(comedians, _, _) do 59 | Enum.map(comedians, fn %{jokes: jokes} -> 60 | Enum.any?(jokes) 61 | end) 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /test/support/resources/comment_link.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.CommentLink do 2 | @moduledoc false 3 | use Ash.Resource, 4 | domain: AshPostgres.Test.Domain, 5 | data_layer: AshPostgres.DataLayer 6 | 7 | postgres do 8 | table "comment_links" 9 | repo AshPostgres.TestRepo 10 | end 11 | 12 | actions do 13 | default_accept(:*) 14 | 15 | defaults([:create, :read, :update, :destroy]) 16 | end 17 | 18 | identities do 19 | identity(:unique_link, [:source_id, :dest_id]) 20 | end 21 | 22 | relationships do 23 | belongs_to :source, AshPostgres.Test.Comment do 24 | public?(true) 25 | allow_nil?(false) 26 | primary_key?(true) 27 | end 28 | 29 | belongs_to :dest, AshPostgres.Test.Comment do 30 | public?(true) 31 | allow_nil?(false) 32 | primary_key?(true) 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /test/support/resources/content.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.Content do 2 | @moduledoc false 3 | use Ash.Resource, 4 | otp_app: :ash_postgres, 5 | domain: AshPostgres.Test.Domain, 6 | data_layer: AshPostgres.DataLayer 7 | 8 | postgres do 9 | table "content" 10 | repo AshPostgres.TestRepo 11 | end 12 | 13 | actions do 14 | defaults([:create, :read, :update, :destroy]) 15 | end 16 | 17 | attributes do 18 | uuid_primary_key(:id) 19 | 20 | timestamps() 21 | end 22 | 23 | relationships do 24 | belongs_to(:note, AshPostgres.Test.Note) 25 | 26 | many_to_many :visibility_groups, AshPostgres.Test.StaffGroup do 27 | through(AshPostgres.Test.ContentVisibilityGroup) 28 | end 29 | end 30 | 31 | aggregates do 32 | list :visibility_group_staff_ids, [:visibility_groups, :members], :id do 33 | uniq?(true) 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /test/support/resources/content_visibility_group.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.ContentVisibilityGroup do 2 | @moduledoc false 3 | use Ash.Resource, 4 | otp_app: :ash_postgres, 5 | domain: AshPostgres.Test.Domain, 6 | data_layer: AshPostgres.DataLayer 7 | 8 | postgres do 9 | table "content_visibility_group" 10 | repo AshPostgres.TestRepo 11 | end 12 | 13 | actions do 14 | defaults([:read, :destroy, create: :*, update: :*]) 15 | end 16 | 17 | relationships do 18 | belongs_to(:content, AshPostgres.Test.Content, primary_key?: true, allow_nil?: false) 19 | belongs_to(:staff_group, AshPostgres.Test.StaffGroup, primary_key?: true, allow_nil?: false) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /test/support/resources/csv.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.CSVColumnMatchingEmbedded do 2 | @moduledoc false 3 | use Ash.Resource, 4 | data_layer: :embedded 5 | 6 | attributes do 7 | attribute(:column, :integer, allow_nil?: true, public?: true) 8 | attribute(:attribute, :string, allow_nil?: true, public?: true) 9 | end 10 | end 11 | 12 | defmodule AshPostgres.Test.CSVColumnMappingNewType do 13 | @moduledoc false 14 | use Ash.Type.NewType, 15 | subtype_of: :map, 16 | constraints: [ 17 | fields: [ 18 | attribute: [type: :string, allow_nil?: false], 19 | column: [type: :integer, allow_nil?: false] 20 | ] 21 | ] 22 | end 23 | 24 | defmodule AshPostgres.Test.CSV do 25 | @moduledoc false 26 | use Ash.Resource, 27 | domain: AshPostgres.Test.Domain, 28 | data_layer: AshPostgres.DataLayer 29 | 30 | alias AshPostgres.Test 31 | 32 | actions do 33 | default_accept(:*) 34 | 35 | defaults([:create, :read, :destroy]) 36 | 37 | update :update do 38 | primary?(true) 39 | accept([:column_mapping_embedded, :column_mapping_new_type]) 40 | # require_atomic?(false) 41 | end 42 | end 43 | 44 | attributes do 45 | uuid_primary_key(:id) 46 | 47 | attribute(:column_mapping_embedded, {:array, Test.CSVColumnMatchingEmbedded}, public?: true) 48 | attribute(:column_mapping_new_type, {:array, Test.CSVColumnMatchingEmbedded}, public?: true) 49 | end 50 | 51 | postgres do 52 | table "csv" 53 | repo(AshPostgres.TestRepo) 54 | 55 | storage_types column_mapping_embedded: :jsonb, column_mapping_new_type: :jsonb 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /test/support/resources/db_point.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.DbPoint do 2 | @moduledoc false 3 | use Ash.Resource, 4 | domain: AshPostgres.Test.Domain, 5 | data_layer: AshPostgres.DataLayer 6 | 7 | postgres do 8 | table("points") 9 | repo(AshPostgres.TestRepo) 10 | end 11 | 12 | actions do 13 | defaults([:read, :destroy]) 14 | 15 | create :create do 16 | primary?(true) 17 | accept([:id]) 18 | upsert?(true) 19 | upsert_identity(:id) 20 | end 21 | end 22 | 23 | attributes do 24 | attribute(:id, AshPostgres.Test.Point) do 25 | public?(true) 26 | primary_key?(true) 27 | allow_nil?(false) 28 | end 29 | end 30 | 31 | identities do 32 | identity(:id, [:id]) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /test/support/resources/db_string_point.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.DbStringPoint do 2 | @moduledoc false 3 | use Ash.Resource, 4 | domain: AshPostgres.Test.Domain, 5 | data_layer: AshPostgres.DataLayer 6 | 7 | postgres do 8 | table("string_points") 9 | repo(AshPostgres.TestRepo) 10 | end 11 | 12 | actions do 13 | defaults([:read, :destroy]) 14 | 15 | create :create do 16 | primary?(true) 17 | accept([:id]) 18 | upsert?(true) 19 | upsert_identity(:id) 20 | end 21 | end 22 | 23 | attributes do 24 | attribute(:id, AshPostgres.Test.StringPoint) do 25 | public?(true) 26 | primary_key?(true) 27 | allow_nil?(false) 28 | end 29 | end 30 | 31 | identities do 32 | identity(:id, [:id]) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /test/support/resources/entity.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.Entity do 2 | @moduledoc false 3 | 4 | use Ash.Resource, 5 | domain: AshPostgres.Test.Domain, 6 | data_layer: AshPostgres.DataLayer 7 | 8 | attributes do 9 | uuid_primary_key(:id) 10 | 11 | attribute(:full_name, :string, allow_nil?: false, public?: true) 12 | 13 | timestamps(public?: true) 14 | end 15 | 16 | postgres do 17 | table "entities" 18 | repo AshPostgres.TestRepo 19 | end 20 | 21 | actions do 22 | default_accept(:*) 23 | 24 | defaults([:create, :read]) 25 | 26 | read :read_from_temp do 27 | prepare(fn query, _ -> 28 | Ash.Query.set_context(query, %{data_layer: %{table: "temp_entities", schema: "temp"}}) 29 | end) 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /test/support/resources/integer_post.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.IntegerPost do 2 | @moduledoc false 3 | use Ash.Resource, 4 | domain: AshPostgres.Test.Domain, 5 | data_layer: AshPostgres.DataLayer 6 | 7 | postgres do 8 | table "integer_posts" 9 | repo AshPostgres.TestRepo 10 | end 11 | 12 | actions do 13 | default_accept(:*) 14 | 15 | defaults([:create, :read, :update, :destroy]) 16 | end 17 | 18 | attributes do 19 | integer_primary_key(:id) 20 | attribute(:title, :string, public?: true) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /test/support/resources/invite.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.Invite do 2 | @moduledoc false 3 | use Ash.Resource, 4 | domain: AshPostgres.Test.Domain, 5 | data_layer: AshPostgres.DataLayer 6 | 7 | actions do 8 | default_accept(:*) 9 | 10 | defaults([:create, :read, :update, :destroy]) 11 | end 12 | 13 | attributes do 14 | uuid_primary_key(:id) 15 | attribute(:name, :string, allow_nil?: false, public?: true) 16 | attribute(:role, AshPostgres.Test.Role, allow_nil?: false, public?: true) 17 | end 18 | 19 | postgres do 20 | table "user_invites" 21 | repo(AshPostgres.TestRepo) 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /test/support/resources/joke.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.Joke do 2 | @moduledoc false 3 | use Ash.Resource, 4 | domain: AshPostgres.Test.Domain, 5 | data_layer: AshPostgres.DataLayer, 6 | authorizers: [Ash.Policy.Authorizer] 7 | 8 | attributes do 9 | uuid_primary_key(:id) 10 | attribute(:text, :string, public?: true) 11 | attribute(:is_good, :boolean, default: false, public?: true) 12 | create_timestamp(:inserted_at, public?: true) 13 | update_timestamp(:updated_at, public?: true) 14 | end 15 | 16 | relationships do 17 | belongs_to(:standup_club, AshPostgres.Test.StandupClub, public?: true) 18 | belongs_to(:comedian, AshPostgres.Test.Comedian, public?: true) 19 | has_many(:punchlines, AshPostgres.Test.Punchline, public?: true) 20 | end 21 | 22 | actions do 23 | defaults([:read]) 24 | end 25 | 26 | aggregates do 27 | count(:punchline_count, :punchlines, public?: true) 28 | end 29 | 30 | postgres do 31 | table("jokes") 32 | repo(AshPostgres.TestRepo) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /test/support/resources/manager.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.Manager do 2 | @moduledoc false 3 | use Ash.Resource, 4 | domain: AshPostgres.Test.Domain, 5 | data_layer: AshPostgres.DataLayer 6 | 7 | postgres do 8 | table("managers") 9 | repo(AshPostgres.TestRepo) 10 | end 11 | 12 | actions do 13 | default_accept(:*) 14 | 15 | defaults([:read, :update, :destroy]) 16 | 17 | create :create do 18 | primary?(true) 19 | argument(:organization_id, :uuid, allow_nil?: false) 20 | 21 | change(manage_relationship(:organization_id, :organization, type: :append_and_remove)) 22 | end 23 | end 24 | 25 | identities do 26 | identity(:uniq_code, :code) 27 | end 28 | 29 | attributes do 30 | uuid_primary_key(:id) 31 | attribute(:name, :string, public?: true) 32 | attribute(:code, :string, allow_nil?: false, public?: true) 33 | attribute(:must_be_present, :string, allow_nil?: false, public?: true) 34 | attribute(:role, :string, public?: true) 35 | end 36 | 37 | relationships do 38 | belongs_to :organization, AshPostgres.Test.Organization do 39 | public?(true) 40 | attribute_writable?(true) 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /test/support/resources/note.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.Note do 2 | @moduledoc false 3 | use Ash.Resource, 4 | otp_app: :ash_postgres, 5 | domain: AshPostgres.Test.Domain, 6 | data_layer: AshPostgres.DataLayer 7 | 8 | postgres do 9 | table "note" 10 | repo AshPostgres.TestRepo 11 | end 12 | 13 | actions do 14 | defaults([:read]) 15 | 16 | read :failing_many_reference do 17 | pagination(keyset?: true, default_limit: 25) 18 | filter(expr(count_nils(content.visibility_group_staff_ids) == 0)) 19 | end 20 | end 21 | 22 | attributes do 23 | uuid_primary_key(:id) 24 | 25 | attribute :body, :string do 26 | allow_nil?(false) 27 | public?(true) 28 | end 29 | 30 | timestamps() 31 | end 32 | 33 | relationships do 34 | has_one(:content, AshPostgres.Test.Content) 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /test/support/resources/permalink.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.Permalink do 2 | @moduledoc false 3 | use Ash.Resource, 4 | domain: AshPostgres.Test.Domain, 5 | data_layer: AshPostgres.DataLayer 6 | 7 | actions do 8 | default_accept(:*) 9 | 10 | defaults([:create, :read]) 11 | end 12 | 13 | attributes do 14 | uuid_primary_key(:id) 15 | end 16 | 17 | relationships do 18 | belongs_to :post, AshPostgres.Test.Post do 19 | public?(true) 20 | allow_nil?(false) 21 | attribute_writable?(true) 22 | end 23 | end 24 | 25 | postgres do 26 | table "post_permalinks" 27 | repo AshPostgres.TestRepo 28 | 29 | references do 30 | reference :post, on_delete: :nothing 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /test/support/resources/post_follower.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.PostFollower do 2 | @moduledoc false 3 | use Ash.Resource, 4 | domain: AshPostgres.Test.Domain, 5 | data_layer: AshPostgres.DataLayer 6 | 7 | postgres do 8 | table "post_followers" 9 | repo AshPostgres.TestRepo 10 | end 11 | 12 | actions do 13 | default_accept(:*) 14 | 15 | defaults([:create, :read, :update, :destroy]) 16 | end 17 | 18 | attributes do 19 | attribute(:order, :integer, public?: true) 20 | end 21 | 22 | relationships do 23 | belongs_to :post, AshPostgres.Test.Post do 24 | primary_key?(true) 25 | public?(true) 26 | allow_nil?(false) 27 | end 28 | 29 | belongs_to :follower, AshPostgres.Test.User do 30 | primary_key?(true) 31 | public?(true) 32 | allow_nil?(false) 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /test/support/resources/post_link.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.PostLink do 2 | @moduledoc false 3 | use Ash.Resource, 4 | domain: AshPostgres.Test.Domain, 5 | data_layer: AshPostgres.DataLayer 6 | 7 | postgres do 8 | table "post_links" 9 | repo AshPostgres.TestRepo 10 | end 11 | 12 | actions do 13 | default_accept(:*) 14 | 15 | defaults([:create, :read, :update, :destroy]) 16 | end 17 | 18 | identities do 19 | identity(:unique_link, [:source_post_id, :destination_post_id]) 20 | end 21 | 22 | attributes do 23 | attribute :state, :atom do 24 | public?(true) 25 | constraints(one_of: [:active, :archived]) 26 | default(:active) 27 | end 28 | end 29 | 30 | relationships do 31 | belongs_to :source_post, AshPostgres.Test.Post do 32 | public?(true) 33 | allow_nil?(false) 34 | primary_key?(true) 35 | end 36 | 37 | belongs_to :destination_post, AshPostgres.Test.Post do 38 | public?(true) 39 | allow_nil?(false) 40 | primary_key?(true) 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /test/support/resources/post_views.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.PostView do 2 | @moduledoc false 3 | use Ash.Resource, 4 | domain: AshPostgres.Test.Domain, 5 | data_layer: AshPostgres.DataLayer 6 | 7 | actions do 8 | default_accept(:*) 9 | 10 | defaults([:create, :read]) 11 | end 12 | 13 | attributes do 14 | create_timestamp(:time) 15 | attribute(:browser, :atom, constraints: [one_of: [:firefox, :chrome, :edge]], public?: true) 16 | end 17 | 18 | relationships do 19 | belongs_to :post, AshPostgres.Test.Post do 20 | public?(true) 21 | allow_nil?(false) 22 | attribute_writable?(true) 23 | end 24 | end 25 | 26 | resource do 27 | require_primary_key?(false) 28 | end 29 | 30 | postgres do 31 | table "post_views" 32 | repo AshPostgres.TestRepo 33 | 34 | references do 35 | reference :post, ignore?: true 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /test/support/resources/post_with_empty_update.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.PostWithEmptyUpdate do 2 | @moduledoc false 3 | use Ash.Resource, 4 | domain: AshPostgres.Test.Domain, 5 | data_layer: AshPostgres.DataLayer, 6 | authorizers: [ 7 | Ash.Policy.Authorizer 8 | ] 9 | 10 | require Ash.Sort 11 | 12 | policies do 13 | policy action(:empty_update) do 14 | # force visiting the database 15 | authorize_if(expr(fragment("TRUE = FALSE"))) 16 | end 17 | end 18 | 19 | postgres do 20 | table("posts") 21 | repo(AshPostgres.TestRepo) 22 | migrate? false 23 | end 24 | 25 | actions do 26 | defaults([:create, :read]) 27 | 28 | update :empty_update do 29 | accept([]) 30 | end 31 | end 32 | 33 | attributes do 34 | uuid_primary_key(:id, writable?: true) 35 | 36 | attribute(:title, :string) do 37 | public?(true) 38 | source(:title_column) 39 | end 40 | 41 | attribute :model, :tuple do 42 | constraints( 43 | fields: [ 44 | alpha: [type: :float, description: "The alpha field"], 45 | beta: [type: :float, description: "The beta field"], 46 | t: [type: :float, description: "The t field"] 47 | ] 48 | ) 49 | 50 | allow_nil?(false) 51 | default(fn -> {3.0, 3.0, 1.0} end) 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /test/support/resources/profile.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.Profile do 2 | @moduledoc false 3 | use Ash.Resource, 4 | domain: AshPostgres.Test.Domain, 5 | data_layer: AshPostgres.DataLayer 6 | 7 | postgres do 8 | table("profile") 9 | schema("profiles") 10 | repo(AshPostgres.TestRepo) 11 | end 12 | 13 | attributes do 14 | uuid_primary_key(:id, writable?: true) 15 | attribute(:description, :string, public?: true) 16 | end 17 | 18 | actions do 19 | default_accept(:*) 20 | 21 | defaults([:create, :read, :update, :destroy]) 22 | 23 | read :by_indirectly_matching_description do 24 | argument :term, :string do 25 | allow_nil?(false) 26 | end 27 | 28 | filter(expr(calc_word_similarity(term: ^arg(:term)) > 0.2)) 29 | end 30 | 31 | read :by_directly_matching_description do 32 | argument :term, :string do 33 | allow_nil?(false) 34 | end 35 | 36 | filter(expr(trigram_word_similarity(description, ^arg(:term)) > 0.2)) 37 | end 38 | end 39 | 40 | calculations do 41 | calculate :calc_word_similarity, 42 | :float, 43 | expr(trigram_word_similarity(description, ^arg(:term))) do 44 | argument(:term, :string, allow_nil?: false) 45 | end 46 | end 47 | 48 | relationships do 49 | belongs_to(:author, AshPostgres.Test.Author) do 50 | public?(true) 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /test/support/resources/punchline.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.Punchline do 2 | @moduledoc false 3 | use Ash.Resource, 4 | domain: AshPostgres.Test.Domain, 5 | data_layer: AshPostgres.DataLayer, 6 | authorizers: [Ash.Policy.Authorizer] 7 | 8 | attributes do 9 | uuid_primary_key(:id) 10 | create_timestamp(:inserted_at, public?: true) 11 | update_timestamp(:updated_at, public?: true) 12 | end 13 | 14 | relationships do 15 | belongs_to(:joke, AshPostgres.Test.Joke, public?: true) 16 | end 17 | 18 | actions do 19 | defaults([:read]) 20 | end 21 | 22 | postgres do 23 | table("punchlines") 24 | repo(AshPostgres.TestRepo) 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /test/support/resources/rating.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.Rating do 2 | @moduledoc false 3 | use Ash.Resource, 4 | domain: AshPostgres.Test.Domain, 5 | data_layer: AshPostgres.DataLayer 6 | 7 | postgres do 8 | polymorphic?(true) 9 | repo AshPostgres.TestRepo 10 | end 11 | 12 | actions do 13 | default_accept(:*) 14 | 15 | defaults([:create, :read, :update, :destroy]) 16 | end 17 | 18 | attributes do 19 | uuid_primary_key(:id) 20 | attribute(:score, :integer, public?: true) 21 | attribute(:resource_id, :uuid, public?: true) 22 | end 23 | 24 | calculations do 25 | calculate(:double_score, :integer, expr(score * 2)) 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/support/resources/record.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.Record do 2 | @moduledoc false 3 | 4 | use Ash.Resource, 5 | domain: AshPostgres.Test.Domain, 6 | data_layer: AshPostgres.DataLayer 7 | 8 | attributes do 9 | uuid_primary_key(:id) 10 | 11 | attribute(:full_name, :string, allow_nil?: false, public?: true) 12 | 13 | timestamps(public?: true) 14 | end 15 | 16 | relationships do 17 | has_one :entity, AshPostgres.Test.Entity do 18 | public?(true) 19 | no_attributes?(true) 20 | 21 | read_action(:read_from_temp) 22 | 23 | filter(expr(full_name == parent(full_name))) 24 | end 25 | 26 | has_one :temp_entity, AshPostgres.Test.TempEntity do 27 | public?(true) 28 | source_attribute(:full_name) 29 | destination_attribute(:full_name) 30 | end 31 | 32 | many_to_many :temp_entities, AshPostgres.Test.TempEntity do 33 | public?(true) 34 | 35 | through(AshPostgres.Test.RecordTempEntity) 36 | end 37 | end 38 | 39 | postgres do 40 | table "records" 41 | repo AshPostgres.TestRepo 42 | end 43 | 44 | calculations do 45 | calculate( 46 | :temp_entity_full_name, 47 | :string, 48 | expr(fragment("coalesce(?, '')", temp_entities.full_name)) 49 | ) 50 | end 51 | 52 | actions do 53 | default_accept(:*) 54 | 55 | defaults([:create, :read, :update]) 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /test/support/resources/record_temp_entity.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.RecordTempEntity do 2 | @moduledoc false 3 | 4 | use Ash.Resource, 5 | domain: AshPostgres.Test.Domain, 6 | data_layer: AshPostgres.DataLayer 7 | 8 | postgres do 9 | table "records_temp_entities" 10 | repo AshPostgres.TestRepo 11 | end 12 | 13 | attributes do 14 | uuid_primary_key(:id) 15 | end 16 | 17 | relationships do 18 | belongs_to(:record, AshPostgres.Test.Record, public?: true) 19 | belongs_to(:temp_entity, AshPostgres.Test.TempEntity, public?: true) 20 | end 21 | 22 | actions do 23 | defaults([:read, :destroy, create: :*, update: :*]) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/support/resources/role.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.Role do 2 | @moduledoc false 3 | 4 | use Ash.Type.Enum, values: [:admin, :user] 5 | end 6 | -------------------------------------------------------------------------------- /test/support/resources/staff_group.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.StaffGroup do 2 | @moduledoc false 3 | use Ash.Resource, 4 | otp_app: :ash_postgres, 5 | domain: AshPostgres.Test.Domain, 6 | data_layer: AshPostgres.DataLayer 7 | 8 | postgres do 9 | table "staff_group" 10 | repo AshPostgres.TestRepo 11 | end 12 | 13 | actions do 14 | defaults([:read, :destroy, create: :*, update: :*]) 15 | end 16 | 17 | attributes do 18 | uuid_primary_key(:id) 19 | 20 | timestamps() 21 | end 22 | 23 | relationships do 24 | many_to_many :members, AshPostgres.Test.User do 25 | through(AshPostgres.Test.StaffGroupMember) 26 | end 27 | end 28 | 29 | aggregates do 30 | count(:members_count, :members) 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /test/support/resources/staff_group_member.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.StaffGroupMember do 2 | @moduledoc false 3 | use Ash.Resource, 4 | otp_app: :ash_postgres, 5 | domain: AshPostgres.Test.Domain, 6 | data_layer: AshPostgres.DataLayer 7 | 8 | postgres do 9 | table "staff_group_member" 10 | repo AshPostgres.TestRepo 11 | end 12 | 13 | actions do 14 | defaults([:read, :destroy, create: :*, update: :*]) 15 | end 16 | 17 | attributes do 18 | create_timestamp(:inserted_at) 19 | end 20 | 21 | relationships do 22 | belongs_to(:staff_group, AshPostgres.Test.StaffGroup, primary_key?: true, allow_nil?: false) 23 | belongs_to(:user, AshPostgres.Test.User, primary_key?: true, allow_nil?: false) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/support/resources/standup_club.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.StandupClub do 2 | @moduledoc false 3 | use Ash.Resource, 4 | domain: AshPostgres.Test.Domain, 5 | data_layer: AshPostgres.DataLayer, 6 | authorizers: [Ash.Policy.Authorizer] 7 | 8 | attributes do 9 | uuid_primary_key(:id) 10 | attribute(:name, :string, public?: true) 11 | create_timestamp(:inserted_at, public?: true) 12 | update_timestamp(:updated_at, public?: true) 13 | end 14 | 15 | relationships do 16 | has_many(:comedians, AshPostgres.Test.Comedian, public?: true) 17 | has_many(:jokes, AshPostgres.Test.Joke, public?: true) 18 | end 19 | 20 | actions do 21 | defaults([:read]) 22 | end 23 | 24 | aggregates do 25 | count(:punchline_count, [:jokes, :punchlines], public?: true) 26 | end 27 | 28 | postgres do 29 | table("standup_clubs") 30 | repo(AshPostgres.TestRepo) 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /test/support/resources/stateful_post_follwer.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.StatefulPostFollower do 2 | @moduledoc false 3 | use Ash.Resource, 4 | domain: AshPostgres.Test.Domain, 5 | data_layer: AshPostgres.DataLayer 6 | 7 | postgres do 8 | table "stateful_post_followers" 9 | repo AshPostgres.TestRepo 10 | end 11 | 12 | identities do 13 | identity(:join_attributes, [:post_id, :follower_id, :state]) 14 | end 15 | 16 | actions do 17 | default_accept(:*) 18 | 19 | defaults([:create, :read, :update, :destroy]) 20 | end 21 | 22 | attributes do 23 | uuid_primary_key(:id) 24 | attribute(:order, :integer, public?: true) 25 | 26 | attribute :state, :atom do 27 | public?(true) 28 | constraints(one_of: [:active, :inactive]) 29 | default(:active) 30 | end 31 | end 32 | 33 | relationships do 34 | belongs_to :post, AshPostgres.Test.Post do 35 | public?(true) 36 | allow_nil?(false) 37 | end 38 | 39 | belongs_to :follower, AshPostgres.Test.User do 40 | public?(true) 41 | allow_nil?(false) 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /test/support/resources/subquery/access.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.Subquery.Access do 2 | @moduledoc false 3 | alias AshPostgres.Test.Subquery.Parent 4 | 5 | use Ash.Resource, 6 | domain: AshPostgres.Test.Subquery.ParentDomain, 7 | data_layer: AshPostgres.DataLayer, 8 | primary_read_warning?: false, 9 | authorizers: [ 10 | Ash.Policy.Authorizer 11 | ] 12 | 13 | require Ash.Query 14 | 15 | postgres do 16 | repo AshPostgres.TestRepo 17 | table "subquery_access" 18 | end 19 | 20 | attributes do 21 | uuid_primary_key(:id) 22 | attribute(:parent_id, :uuid, public?: true) 23 | attribute(:email, :string, public?: true) 24 | end 25 | 26 | code_interface do 27 | define(:create) 28 | define(:read) 29 | end 30 | 31 | relationships do 32 | belongs_to(:parent, Parent) do 33 | public?(true) 34 | end 35 | end 36 | 37 | policies do 38 | policy always() do 39 | authorize_if(always()) 40 | end 41 | end 42 | 43 | actions do 44 | default_accept(:*) 45 | 46 | defaults([:create, :update, :destroy]) 47 | 48 | read :read do 49 | primary?(true) 50 | 51 | prepare(fn query, %{actor: actor} -> 52 | # THIS CAUSES THE ERROR 53 | query 54 | |> Ash.Query.filter(parent.visible == true) 55 | end) 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /test/support/resources/subquery/child.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.Subquery.Child do 2 | @moduledoc false 3 | alias AshPostgres.Test.Subquery.Through 4 | 5 | use Ash.Resource, 6 | domain: AshPostgres.Test.Subquery.ChildDomain, 7 | data_layer: AshPostgres.DataLayer, 8 | authorizers: [ 9 | Ash.Policy.Authorizer 10 | ] 11 | 12 | postgres do 13 | repo AshPostgres.TestRepo 14 | table "subquery_child" 15 | end 16 | 17 | attributes do 18 | uuid_primary_key(:id) 19 | attribute(:state, :string, public?: true) 20 | end 21 | 22 | code_interface do 23 | define(:create) 24 | define(:read) 25 | end 26 | 27 | relationships do 28 | has_many :throughs, Through do 29 | public?(true) 30 | source_attribute(:id) 31 | destination_attribute(:child_id) 32 | end 33 | end 34 | 35 | policies do 36 | policy [ 37 | action(:read), 38 | expr( 39 | (not is_nil(^actor(:email)) and 40 | (exists(throughs.parent, owner_email == ^actor(:email)) or 41 | exists(throughs.parent, other_owner_email == ^actor(:email)) or 42 | exists(throughs.parent.accesses, email == ^actor(:email)))) or 43 | state in ["public", "open"] 44 | ) 45 | ] do 46 | authorize_if(always()) 47 | end 48 | end 49 | 50 | actions do 51 | default_accept(:*) 52 | 53 | defaults([:create, :read, :update, :destroy]) 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /test/support/resources/subquery/child_domain.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.Subquery.ChildDomain do 2 | @moduledoc false 3 | alias AshPostgres.Test.Subquery.Child 4 | alias AshPostgres.Test.Subquery.Through 5 | use Ash.Domain 6 | 7 | resources do 8 | resource(Child) 9 | resource(Through) 10 | end 11 | 12 | authorization do 13 | authorize(:when_requested) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/support/resources/subquery/parent_domain.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.Subquery.ParentDomain do 2 | @moduledoc false 3 | alias AshPostgres.Test.Subquery.Access 4 | alias AshPostgres.Test.Subquery.Parent 5 | use Ash.Domain 6 | 7 | resources do 8 | resource(Parent) 9 | resource(Access) 10 | end 11 | 12 | authorization do 13 | authorize(:when_requested) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/support/resources/subquery/through.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.Subquery.Through do 2 | @moduledoc false 3 | alias AshPostgres.Test.Subquery.Child 4 | alias AshPostgres.Test.Subquery.Parent 5 | alias AshPostgres.Test.Subquery.ParentDomain 6 | 7 | use Ash.Resource, 8 | domain: AshPostgres.Test.Subquery.ChildDomain, 9 | data_layer: AshPostgres.DataLayer, 10 | authorizers: [ 11 | Ash.Policy.Authorizer 12 | ] 13 | 14 | postgres do 15 | repo AshPostgres.TestRepo 16 | table "subquery_through" 17 | end 18 | 19 | attributes do 20 | attribute :parent_id, :uuid do 21 | public?(true) 22 | primary_key?(true) 23 | allow_nil?(false) 24 | end 25 | 26 | attribute :child_id, :uuid do 27 | public?(true) 28 | primary_key?(true) 29 | allow_nil?(false) 30 | end 31 | end 32 | 33 | code_interface do 34 | define(:create) 35 | define(:read) 36 | end 37 | 38 | relationships do 39 | belongs_to :parent, Parent do 40 | public?(true) 41 | domain(ParentDomain) 42 | end 43 | 44 | belongs_to :child, Child do 45 | public?(true) 46 | source_attribute(:parent_id) 47 | destination_attribute(:id) 48 | end 49 | end 50 | 51 | policies do 52 | policy always() do 53 | authorize_if(always()) 54 | end 55 | end 56 | 57 | actions do 58 | default_accept(:*) 59 | 60 | defaults([:create, :read, :update, :destroy]) 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /test/support/resources/temp_entity.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.TempEntity do 2 | @moduledoc false 3 | 4 | use Ash.Resource, 5 | domain: AshPostgres.Test.Domain, 6 | data_layer: AshPostgres.DataLayer 7 | 8 | attributes do 9 | uuid_primary_key(:id) 10 | 11 | attribute(:full_name, :string, allow_nil?: false, public?: true) 12 | 13 | timestamps(public?: true) 14 | end 15 | 16 | postgres do 17 | table "temp_entities" 18 | schema "temp" 19 | repo AshPostgres.TestRepo 20 | end 21 | 22 | actions do 23 | default_accept(:*) 24 | 25 | defaults([:create, :read]) 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/support/string_agg.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.StringAgg do 2 | @moduledoc false 3 | use Ash.Resource.Aggregate.CustomAggregate 4 | use AshPostgres.CustomAggregate 5 | 6 | require Ecto.Query 7 | 8 | def dynamic(opts, binding) do 9 | Ecto.Query.dynamic( 10 | [], 11 | fragment("string_agg(?, ?)", field(as(^binding), ^opts[:field]), ^opts[:delimiter]) 12 | ) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /test/support/test_app.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestApp do 2 | @moduledoc false 3 | def start(_type, _args) do 4 | children = [ 5 | AshPostgres.TestRepo 6 | ] 7 | 8 | # See https://hexdocs.pm/elixir/Supervisor.html 9 | # for other strategies and supported options 10 | opts = [strategy: :one_for_one, name: AshPostgres.Supervisor] 11 | Supervisor.start_link(children, opts) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/support/test_custom_extension.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestCustomExtension do 2 | @moduledoc false 3 | 4 | use AshPostgres.CustomExtension, name: "demo-functions", latest_version: 1 5 | 6 | @impl true 7 | def install(0) do 8 | """ 9 | execute(\"\"\" 10 | CREATE OR REPLACE FUNCTION ash_demo_functions() 11 | RETURNS boolean AS $$ SELECT TRUE $$ 12 | LANGUAGE SQL 13 | IMMUTABLE; 14 | \"\"\") 15 | """ 16 | end 17 | 18 | @impl true 19 | def install(1) do 20 | """ 21 | execute(\"\"\" 22 | CREATE OR REPLACE FUNCTION ash_demo_functions() 23 | RETURNS boolean AS $$ SELECT FALSE $$ 24 | LANGUAGE SQL 25 | IMMUTABLE; 26 | \"\"\") 27 | """ 28 | end 29 | 30 | @impl true 31 | def uninstall(_version) do 32 | """ 33 | execute(\"\"\" 34 | DROP FUNCTION IF EXISTS ash_demo_functions() 35 | \"\"\") 36 | """ 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /test/support/test_no_sandbox_repo.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestNoSandboxRepo do 2 | @moduledoc false 3 | use AshPostgres.Repo, 4 | otp_app: :ash_postgres 5 | 6 | def on_transaction_begin(data) do 7 | send(self(), data) 8 | end 9 | 10 | def min_pg_version do 11 | case System.get_env("PG_VERSION") do 12 | nil -> 13 | %Version{major: 16, minor: 0, patch: 0} 14 | 15 | version -> 16 | case Integer.parse(version) do 17 | {major, ""} -> %Version{major: major, minor: 0, patch: 0} 18 | _ -> Version.parse!(version) 19 | end 20 | end 21 | end 22 | 23 | def installed_extensions do 24 | ["ash-functions", "uuid-ossp", "pg_trgm", "citext", AshPostgres.TestCustomExtension] -- 25 | Application.get_env(:ash_postgres, :no_extensions, []) 26 | end 27 | 28 | def all_tenants do 29 | [] 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /test/support/test_repo.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.TestRepo do 2 | @moduledoc false 3 | use AshPostgres.Repo, 4 | otp_app: :ash_postgres 5 | 6 | def on_transaction_begin(data) do 7 | send(self(), data) 8 | end 9 | 10 | def prefer_transaction?, do: false 11 | 12 | def prefer_transaction_for_atomic_updates?, do: false 13 | 14 | def installed_extensions do 15 | ["ash-functions", "uuid-ossp", "pg_trgm", "citext", AshPostgres.TestCustomExtension, "ltree"] -- 16 | Application.get_env(:ash_postgres, :no_extensions, []) 17 | end 18 | 19 | def min_pg_version do 20 | case System.get_env("PG_VERSION") do 21 | nil -> 22 | %Version{major: 16, minor: 0, patch: 0} 23 | 24 | version -> 25 | case Integer.parse(version) do 26 | {major, ""} -> %Version{major: major, minor: 0, patch: 0} 27 | _ -> Version.parse!(version) 28 | end 29 | end 30 | end 31 | 32 | def all_tenants do 33 | Code.ensure_compiled(AshPostgres.MultitenancyTest.Org) 34 | 35 | AshPostgres.MultitenancyTest.Org 36 | |> Ash.read!() 37 | |> Enum.map(&"org_#{&1.id}") 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /test/support/trigram_word_similarity.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Expressions.TrigramWordSimilarity do 2 | @moduledoc false 3 | use Ash.CustomExpression, 4 | name: :trigram_word_similarity, 5 | arguments: [[:string, :string]], 6 | # setting to true does not seem to change the behaviour observed in this ticket 7 | predicate?: false 8 | 9 | def expression(data_layer, [left, right]) when data_layer in [AshPostgres.DataLayer] do 10 | {:ok, expr(fragment("word_similarity(?, ?)", ^left, ^right))} 11 | end 12 | 13 | def expression(_data_layer, _args), do: :unknown 14 | end 15 | -------------------------------------------------------------------------------- /test/support/types/composite_point.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.CompositePoint do 2 | @moduledoc false 3 | use Ash.Type 4 | 5 | def storage_type(_), do: :custom_point 6 | 7 | def composite?(_constraints) do 8 | true 9 | end 10 | 11 | def composite_types(_constraints) do 12 | [{:x, :integer, []}, {:y, :integer, []}] 13 | end 14 | 15 | def cast_input(nil, _), do: {:ok, nil} 16 | 17 | def cast_input(%{x: a, y: b}, _) when is_integer(a) and is_integer(b) do 18 | {:ok, %{x: a, y: b}} 19 | end 20 | 21 | def cast_input({a, b}, _) when is_integer(a) and is_integer(b) do 22 | {:ok, %{x: a, y: b}} 23 | end 24 | 25 | def cast_input(_, _), do: :error 26 | 27 | def cast_stored(nil, _), do: {:ok, nil} 28 | 29 | def cast_stored(%{x: a, y: b}, _) when is_integer(a) and is_integer(b) do 30 | {:ok, %{x: a, y: b}} 31 | end 32 | 33 | def cast_stored({a, b}, _) when is_integer(a) and is_integer(b) do 34 | {:ok, %{x: a, y: b}} 35 | end 36 | 37 | def cast_stored(_, _) do 38 | :error 39 | end 40 | 41 | def dump_to_native(nil, _), do: {:ok, nil} 42 | 43 | def dump_to_native(%{x: a, y: b}, _) when is_integer(a) and is_integer(b) do 44 | {:ok, {a, b}} 45 | end 46 | 47 | def dump_to_native(_, _) do 48 | :error 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /test/support/types/email.ex: -------------------------------------------------------------------------------- 1 | defmodule Test.Support.Types.Email do 2 | @moduledoc false 3 | use Ash.Type.NewType, 4 | subtype_of: :ci_string, 5 | constraints: [ 6 | casing: :lower 7 | ] 8 | end 9 | -------------------------------------------------------------------------------- /test/support/types/money.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.Money do 2 | @moduledoc false 3 | use Ash.Resource, 4 | data_layer: :embedded 5 | 6 | attributes do 7 | attribute :amount, :integer do 8 | public?(true) 9 | allow_nil?(false) 10 | constraints(min: 0) 11 | end 12 | 13 | attribute :currency, :atom do 14 | public?(true) 15 | constraints(one_of: [:eur, :usd]) 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /test/support/types/person_detail.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.PersonDetail do 2 | @moduledoc """ 3 | A tuple type for testing Ash.Type.Tuple 4 | """ 5 | use Ash.Type.NewType, 6 | subtype_of: :tuple, 7 | constraints: [ 8 | fields: [ 9 | first_name: [type: :string, allow_nil?: false], 10 | last_name: [type: :string, allow_nil?: false] 11 | ] 12 | ] 13 | end 14 | -------------------------------------------------------------------------------- /test/support/types/point.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.Point do 2 | @moduledoc false 3 | use Ash.Type 4 | 5 | def storage_type(_), do: {:array, :float} 6 | 7 | def cast_input(nil, _), do: {:ok, nil} 8 | 9 | def cast_input({a, b, c}, _) when is_float(a) and is_float(b) and is_float(c) do 10 | {:ok, {a, b, c}} 11 | end 12 | 13 | def cast_input(_, _), do: :error 14 | 15 | def cast_stored(nil, _), do: {:ok, nil} 16 | 17 | def cast_stored([a, b, c], _) when is_float(a) and is_float(b) and is_float(c) do 18 | {:ok, {a, b, c}} 19 | end 20 | 21 | def cast_stored(_, _) do 22 | :error 23 | end 24 | 25 | def dump_to_native(nil, _), do: {:ok, nil} 26 | 27 | def dump_to_native({a, b, c}, _) when is_float(a) and is_float(b) and is_float(c) do 28 | {:ok, [a, b, c]} 29 | end 30 | 31 | def dump_to_native(_, _) do 32 | :error 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /test/support/types/status.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.Types.Status do 2 | @moduledoc false 3 | use Ash.Type.Enum, values: [:open, :closed] 4 | 5 | def storage_type, do: :string 6 | end 7 | -------------------------------------------------------------------------------- /test/support/types/status_enum.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.Types.StatusEnum do 2 | @moduledoc false 3 | use Ash.Type.Enum, values: [:open, :closed] 4 | 5 | def storage_type, do: :status 6 | end 7 | -------------------------------------------------------------------------------- /test/support/types/status_enum_no_cast.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.Types.StatusEnumNoCast do 2 | @moduledoc false 3 | use Ash.Type.Enum, values: [:open, :closed] 4 | 5 | @impl true 6 | def storage_type, do: :status 7 | 8 | @impl true 9 | def cast_in_query?(_), do: false 10 | end 11 | -------------------------------------------------------------------------------- /test/support/types/string_point.ex: -------------------------------------------------------------------------------- 1 | defmodule AshPostgres.Test.StringPoint do 2 | @moduledoc false 3 | use Ash.Type 4 | 5 | defstruct [:x, :y, :z] 6 | 7 | @type t :: %__MODULE__{ 8 | x: float(), 9 | y: float(), 10 | z: float() 11 | } 12 | 13 | def storage_type(_), do: :string 14 | 15 | def cast_input(nil, _), do: {:ok, nil} 16 | 17 | def cast_input(%__MODULE__{} = a, _) do 18 | {:ok, a} 19 | end 20 | 21 | def cast_input({x, y, z}, _) when is_float(x) and is_float(y) and is_float(z) do 22 | {:ok, %__MODULE__{x: x, y: y, z: z}} 23 | end 24 | 25 | def cast_input(enc, _) when is_binary(enc) do 26 | {:ok, parse!(enc)} 27 | end 28 | 29 | def cast_input(_, _), do: :error 30 | 31 | def cast_stored(nil, _), do: {:ok, nil} 32 | 33 | def cast_stored(enc, _) when is_binary(enc) do 34 | {:ok, parse!(enc)} 35 | end 36 | 37 | def cast_stored(_, _) do 38 | :error 39 | end 40 | 41 | def dump_to_native(nil, _), do: {:ok, nil} 42 | 43 | def dump_to_native(%__MODULE__{x: x, y: y, z: z}, _) do 44 | enc = Enum.map_join([x, y, z], ",", &Float.to_string/1) 45 | 46 | {:ok, enc} 47 | end 48 | 49 | def dump_to_native(_, _) do 50 | :error 51 | end 52 | 53 | defp parse!(enc) when is_binary(enc) do 54 | [x, y, z] = 55 | String.split(enc, ",") 56 | |> Enum.map(&String.to_float/1) 57 | 58 | %__MODULE__{x: x, y: y, z: z} 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start(capture_log: true) 2 | 3 | Logger.configure(level: :debug) 4 | 5 | exclude_tags = 6 | case System.get_env("PG_VERSION") do 7 | "13" -> 8 | [:postgres_14, :postgres_15, :postgres_16] 9 | 10 | "14" -> 11 | [:postgres_15, :postgres_16] 12 | 13 | "15" -> 14 | [:postgres_16] 15 | 16 | _ -> 17 | [] 18 | end 19 | 20 | ExUnit.configure(stacktrace_depth: 100, exclude: exclude_tags) 21 | 22 | AshPostgres.TestRepo.start_link() 23 | AshPostgres.DevTestRepo.start_link() 24 | AshPostgres.TestNoSandboxRepo.start_link() 25 | 26 | format_sql_query = 27 | try do 28 | case System.shell("which pg_format") do 29 | {_, 0} -> 30 | fn query -> 31 | try do 32 | case System.shell("echo $SQL_QUERY | pg_format -", 33 | env: [{"SQL_QUERY", query}], 34 | stderr_to_stdout: true 35 | ) do 36 | {formatted_query, 0} -> String.trim_trailing(formatted_query) 37 | _ -> query 38 | end 39 | rescue 40 | _ -> query 41 | end 42 | end 43 | 44 | _ -> 45 | & &1 46 | end 47 | rescue 48 | _ -> 49 | & &1 50 | end 51 | 52 | Ecto.DevLogger.install(AshPostgres.TestRepo, before_inline_callback: format_sql_query) 53 | Ecto.DevLogger.install(AshPostgres.DevTestRepo, before_inline_callback: format_sql_query) 54 | Ecto.DevLogger.install(AshPostgres.TestNoSandboxRepo, before_inline_callback: format_sql_query) 55 | --------------------------------------------------------------------------------