├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md └── workflows │ ├── deploy_documentation.yml │ ├── main.yaml │ └── pypi-publish.yaml ├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── bin └── README.txt ├── conf └── obograph-style.json ├── config.env ├── db └── README.md ├── demo └── cam │ └── README.md ├── docgen-templates ├── class.md.jinja2 ├── common_metadata.md.jinja2 ├── enum.md.jinja2 ├── index.md.jinja2 ├── index.tex.jinja2 ├── schema.md.jinja2 ├── slot.md.jinja2 ├── subset.md.jinja2 └── type.md.jinja2 ├── indexes └── all-indexes.sql ├── install.Makefile ├── mkdocs.yml ├── notebooks └── SemanticSQL-Tutorial.ipynb ├── ontologies.Makefile ├── poetry.lock ├── pyproject.toml ├── reports └── obo.tsv ├── sparql └── skos-to-owl.ru ├── src └── semsql │ ├── __init__.py │ ├── builder │ ├── build.Makefile │ ├── builder.py │ ├── cli.py │ ├── exclude-terms.txt │ ├── indexes │ │ └── all-indexes.sql │ ├── prefixes │ │ ├── obo_prefixes.csv │ │ ├── obo_prefixes.db │ │ ├── obo_prefixes.owl │ │ ├── obo_prefixes.ttl │ │ ├── prefix.sql │ │ ├── prefix_ddl.sql │ │ ├── prefixes.csv │ │ ├── prefixes_curated.csv │ │ └── prefixes_local.csv │ ├── registry │ │ ├── __init__.py │ │ ├── ontologies.yaml │ │ ├── registry_schema.py │ │ └── registry_schema.yaml │ └── sql_schema │ │ ├── obo.sql │ │ ├── omo.sql │ │ ├── owl.sql │ │ ├── rdf.sql │ │ ├── relation_graph.sql │ │ └── semsql.sql │ ├── linkml │ ├── __init__.py │ ├── basics.yaml │ ├── chebi.yaml │ ├── nlp.yaml │ ├── obo.yaml │ ├── omo.yaml │ ├── owl.yaml │ ├── rdf.yaml │ ├── relation_graph.yaml │ ├── ro.yaml │ ├── semsql.yaml │ ├── similarity.yaml │ ├── taxon_constraints.yaml │ └── term_associations.yaml │ ├── loader.py │ ├── ontlib │ ├── README.md │ ├── __init__.py │ ├── common_queries.py │ └── subgraph.py │ ├── sqla │ ├── README.md │ ├── __init__.py │ ├── nlp.py │ ├── obo.py │ ├── omo.py │ ├── owl.py │ ├── rdf.py │ ├── relation_graph.py │ └── semsql.py │ ├── sqlutils │ ├── __init__.py │ ├── reportgen.py │ ├── view2table.py │ └── viewgen.py │ └── utils │ ├── __init__.py │ └── makefile_utils.py ├── tests ├── __init__.py ├── inputs │ ├── go-nucleus.db │ ├── go-nucleus.json │ ├── go-nucleus.owl │ ├── robot-example.db │ └── robot-example.owl ├── integration │ ├── Makefile │ ├── README.md │ └── conf │ │ └── bad-prefixes.csv ├── outputs │ └── README.md ├── test_builder │ ├── __init__.py │ ├── test_builder.py │ └── test_cli.py ├── test_ontlib │ ├── test_common_queries.py │ └── test_subgraph.py ├── test_orm │ ├── __init__.py │ ├── test_basic_sqla.py │ ├── test_crud.py │ └── test_problems.py └── test_sqlutils │ ├── __init__.py │ └── test_generate_views.py ├── tox.ini ├── utils ├── create-semsql-db.sh ├── gaf.header.tsv ├── gaf2tsv ├── gpi2tsv ├── ncbo2owl.pl ├── stem.py ├── subgraph ├── table-from-view.pl └── vsubgraph └── views └── bfo.sql /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | cjm@berkeleybop.org (Christopher J Mungall). 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series of 86 | actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or permanent 93 | ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within the 113 | community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.1, available at 119 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 120 | 121 | Community Impact Guidelines were inspired by 122 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 123 | 124 | For answers to common questions about this code of conduct, see the FAQ at 125 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 126 | [https://www.contributor-covenant.org/translations][translations]. 127 | 128 | [homepage]: https://www.contributor-covenant.org 129 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 130 | [Mozilla CoC]: https://github.com/mozilla/diversity 131 | [FAQ]: https://www.contributor-covenant.org/faq 132 | [translations]: https://www.contributor-covenant.org/translations 133 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ### Code Style 4 | 5 | This project uses [`black`](https://github.com/psf/black) to automatically 6 | enforce a consistent code style. You can apply `black` and other pre-configured 7 | linters with `tox -e lint`. 8 | 9 | This project uses [`flake8`](https://flake8.pycqa.org) and several plugins for 10 | additional checks of documentation style, security issues, good variable 11 | nomenclature, and more ( 12 | see [`tox.ini`](tox.ini) for a list of flake8 plugins). You can check if your 13 | code passes `flake8` with `tox -e flake8`. 14 | 15 | Each of these checks are run on each commit using GitHub Actions as a continuous 16 | integration service. Passing all of them is required for accepting a 17 | contribution. If you're unsure how to address the feedback from one of these 18 | tools, please say so either in the description of your pull request or in a 19 | comment, and we will help you. 20 | 21 | These code style contribution guidelines have been adapted from the 22 | [cthoyt/cookiecutter-snekpack](https://github.com/cthoyt/cookiecutter-snekpack/blob/main/%7B%7Bcookiecutter.package_name%7D%7D/.github/CODE_OF_CONDUCT.md) 23 | Python package template. 24 | -------------------------------------------------------------------------------- /.github/workflows/deploy_documentation.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Documentation 2 | 3 | # Controls when the action will run. Triggers the workflow on push 4 | on: 5 | workflow_dispatch: 6 | push: 7 | branches: [ main ] 8 | 9 | paths: 10 | - 'src/docs/*' 11 | - 'src/schema/*.yaml' 12 | 13 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 14 | jobs: 15 | build-docs: 16 | # The type of runner that the job will run on 17 | runs-on: ubuntu-latest 18 | 19 | # Steps represent a sequence of tasks that will be executed as part of the job 20 | steps: 21 | #---------------------------------------------- 22 | # check-out repo and set-up python 23 | #---------------------------------------------- 24 | - name: Check out repository 25 | uses: actions/checkout@v3 26 | with: 27 | # persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal token 28 | fetch-depth: 0 # otherwise, you will failed to push refs to dest repo 29 | 30 | - name: Set up Python3 31 | uses: actions/setup-python@v3 32 | with: 33 | python-version: 3.9 34 | 35 | #---------------------------------------------- 36 | # install & configure poetry 37 | #---------------------------------------------- 38 | - name: Install Poetry 39 | uses: snok/install-poetry@v1.3 40 | 41 | #---------------------------------------------- 42 | # install dependencies if cache does not exist 43 | #---------------------------------------------- 44 | - name: Install dependencies 45 | # if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' 46 | run: poetry install --no-interaction 47 | 48 | #---------------------------------------------- 49 | # Create documentation and deploy. 50 | #---------------------------------------------- 51 | - name: Create local docs 52 | run: | 53 | mkdir docs 54 | touch docs/.nojekyll 55 | cp -r src/docs/* docs/ 56 | make gendoc 57 | make mkd-gh-deploy 58 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | # Built from: 2 | # https://docs.github.com/en/actions/guides/building-and-testing-python 3 | # https://github.com/snok/install-poetry#workflows-and-tips 4 | 5 | name: Build and test 6 | 7 | on: 8 | push: 9 | branches: [ main ] 10 | pull_request: 11 | branches: [ main ] 12 | 13 | jobs: 14 | lint: 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | python-version: [ '3.9' ] 19 | steps: 20 | - uses: actions/checkout@v2 21 | - name: Set up Python ${{ matrix.python-version }} 22 | uses: actions/setup-python@v2 23 | with: 24 | python-version: ${{ matrix.python-version }} 25 | - name: Install dependencies 26 | run: | 27 | pip install tox 28 | - name: Check code quality with flake8 29 | run: tox -e flake8 30 | 31 | test: 32 | runs-on: ubuntu-latest 33 | strategy: 34 | matrix: 35 | python-version: [ '3.9' ] 36 | 37 | steps: 38 | 39 | #---------------------------------------------- 40 | # check-out repo and set-up python 41 | #---------------------------------------------- 42 | - name: Check out repository 43 | uses: actions/checkout@v3 44 | 45 | - name: Set up Python ${{ matrix.python-version }} 46 | uses: actions/setup-python@v3 47 | with: 48 | python-version: ${{ matrix.python-version }} 49 | 50 | #---------------------------------------------- 51 | # install & configure poetry 52 | #---------------------------------------------- 53 | - name: Install Poetry 54 | uses: snok/install-poetry@v1.3 55 | with: 56 | virtualenvs-create: true 57 | virtualenvs-in-project: true 58 | 59 | #---------------------------------------------- 60 | # load cached venv if cache exists 61 | #---------------------------------------------- 62 | - name: Load cached venv 63 | id: cached-poetry-dependencies 64 | uses: actions/cache@v3 65 | with: 66 | path: .venv 67 | key: venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }} 68 | 69 | #---------------------------------------------- 70 | # install dependencies if cache does not exist 71 | #---------------------------------------------- 72 | - name: Install dependencies 73 | if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' 74 | run: poetry install --no-interaction --no-root 75 | 76 | #---------------------------------------------- 77 | # install your root project, if required 78 | #---------------------------------------------- 79 | - name: Install library 80 | run: poetry install --no-interaction 81 | 82 | #---------------------------------------------- 83 | # run test suite 84 | #---------------------------------------------- 85 | - name: Run tests 86 | run: poetry run python -m unittest discover 87 | 88 | -------------------------------------------------------------------------------- /.github/workflows/pypi-publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish Python Package 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | build-n-publish: 9 | name: Build and publish Python 🐍 distributions 📦 to PyPI 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - name: Set up Python 16 | uses: actions/setup-python@v3 17 | with: 18 | python-version: 3.9 19 | 20 | - name: Install Poetry 21 | uses: snok/install-poetry@v1.3.1 22 | with: 23 | virtualenvs-create: true 24 | virtualenvs-in-project: true 25 | 26 | - name: Install dependencies 27 | run: poetry install --no-interaction 28 | 29 | - name: Build source and wheel archives 30 | run: | 31 | poetry version $(git describe --tags --abbrev=0) 32 | poetry build 33 | 34 | - name: Publish distribution 📦 to PyPI 35 | uses: pypa/gh-action-pypi-publish@v1.2.2 36 | with: 37 | user: __token__ 38 | password: ${{ secrets.pypi_password }} 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | owl/*.owl 3 | download/ 4 | db/*.db 5 | docs/ 6 | load*-* 7 | schemaload*-* 8 | venv 9 | target/ 10 | test.db 11 | inferences/*tsv 12 | inferences/*owl 13 | inferences/*ttl 14 | *.load 15 | __pycache__ 16 | *.old 17 | #.*# 18 | site/ 19 | .DS_Store 20 | bin/rdftab 21 | relation-graph/* 22 | */.DS_Store 23 | inferences/* 24 | node_modules/* 25 | demo/ 26 | *pyc 27 | tmp/ 28 | .template.db 29 | ./owl/ 30 | reports/ 31 | db/ 32 | notesbooks/*.db 33 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM obolibrary/odkfull 2 | LABEL maintainer="cjmungall@lbl.gov" \ 3 | name="semanticsql" \ 4 | version="0.0.1" 5 | 6 | WORKDIR /semsql 7 | 8 | RUN pip install semsql 9 | 10 | COPY *Makefile . 11 | COPY reports/obo.tsv ./reports/ 12 | 13 | 14 | CMD [ "make -k all RUN=" ] 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2024, INCATools 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | OBO = http://purl.obolibrary.org/obo 2 | RUN = poetry run 3 | VERSION = $(shell git tag | tail -1) 4 | SRC_DIR = src/semsql/linkml 5 | BUILDER_DIR = src/semsql/builder 6 | DDL_DIR = $(BUILDER_DIR)/sql_schema 7 | YAML_DIR = src/semsql/linkml 8 | SQLA_DIR = src/semsql/sqla 9 | ONT_REGISTRY = src/semsql/builder/registry/ontologies.yaml 10 | 11 | PREFIX_DIR = $(BUILDER_DIR)/prefixes 12 | 13 | 14 | ALL_OBO_ONTS := $(shell cat reports/obo.tsv) 15 | SELECTED_ONTS = obi mondo go envo ro hp mp zfa wbphenotype ecto upheno uberon_cm doid chebi pr wbphenotype fbbt dron 16 | 17 | # EXTRA_ONTOLOGIES is defined in ontologies.Makefile 18 | ALL_ONTS = $(ALL_OBO_ONTS) $(EXTRA_ONTOLOGIES) 19 | 20 | STAGED_ONTOLOGIES = $(patsubst %, stage/%.db.gz, $(ALL_ONTS)) 21 | 22 | TEST_ONTOLOGIES = go-nucleus robot-example 23 | 24 | # environment variables 25 | include config.env 26 | 27 | GEN_PARGS = 28 | ifdef LINKML_GENERATORS_PROJECT_ARGS 29 | GEN_PARGS = ${LINKML_GENERATORS_PROJECT_ARGS} 30 | endif 31 | 32 | GEN_DARGS = 33 | ifdef LINKML_GENERATORS_MARKDOWN_ARGS 34 | GEN_DARGS = ${LINKML_GENERATORS_MARKDOWN_ARGS} 35 | endif 36 | 37 | 38 | include ontologies.Makefile 39 | 40 | all: build_all stage_all 41 | build_all: $(patsubst %, all-%, $(ALL_ONTS)) 42 | echo building $(ALL_ONTS) 43 | stats_all: $(patsubst %, statistics/%.statistics.yaml, $(ALL_ONTS)) 44 | echo building $(ALL_ONTS) 45 | validate_all: $(patsubst %, validation/%.validate.tsv, $(ALL_ONTS)) 46 | echo building $(ALL_ONTS) 47 | stage_all: $(STAGED_ONTOLOGIES) 48 | echo done $(STAGED_ONTOLOGIES) 49 | 50 | selected: $(patsubst %,all-%,$(SELECTED_ONTS)) 51 | 52 | all-%: db/%.db 53 | sqlite3 $< "SELECT COUNT(*) FROM statements" 54 | stage/%.db.gz: STAMP 55 | gzip -c db/$*.db > $@.tmp && mv $@.tmp $@ 56 | #stage/%.db.gz: 57 | # gzip -c db/$*.db > $@.tmp && mv $@.tmp $@ 58 | .PRECIOUS: stage/%.db.gz 59 | 60 | list-onts: 61 | echo $(ALL_ONTS) 62 | list-extra: 63 | echo $(EXTRA_ONTOLOGIES) 64 | list-staged: 65 | ls -alt $(STAGED_ONTOLOGIES) 66 | 67 | # INSTALL 68 | include install.Makefile 69 | #include ontologies.Makefile 70 | 71 | # --- 72 | # tests 73 | # --- 74 | #test: test_build unittest 75 | test: unittest 76 | unittest: 77 | $(RUN) python -s -m unittest tests/test_*/test_*.py 78 | 79 | cp-%: tests/inputs/%.owl 80 | cp $< owl/ 81 | 82 | test_build: setup_tests $(patsubst %, test-build-%,$(TEST_ONTOLOGIES)) 83 | 84 | test-build-%: inferences/%-inf.tsv 85 | ./utils/create-semsql-db.sh -v -f -r -d db/$*.db owl/$*.owl && cp db/$*.db tests/inputs/ 86 | 87 | # copy from tests/input to staging area 88 | setup_tests: $(patsubst %, cp-%,$(TEST_ONTOLOGIES)) 89 | 90 | realclean-%: 91 | rm target/$*.* ; 92 | rm db/$*.db 93 | 94 | # --- 95 | # Prefixes 96 | # --- 97 | 98 | build_prefixes: $(PREFIX_DIR)/prefixes.csv $(PREFIX_DIR)/prefixes.yaml 99 | 100 | $(PREFIX_DIR)/obo_prefixes.owl: $(STAMP) 101 | robot convert -I http://purl.obolibrary.org/meta/obo_prefixes.ttl -o $@ 102 | 103 | $(PREFIX_DIR)/obo_prefixes.db: $(PREFIX_DIR)/obo_prefixes.owl 104 | sqlite3 $@ < $(PREFIX_DIR)/prefix_ddl.sql && rdftab $@ < $< 105 | 106 | $(PREFIX_DIR)/obo_prefixes.csv: $(PREFIX_DIR)/obo_prefixes.db 107 | sqlite3 $< -cmd ".separator ','" "SELECT p.value AS prefix, ns.value AS base FROM statements AS p JOIN statements AS ns ON (p.subject=ns.subject) WHERE p.predicate='' AND ns.predicate=''" > $@ 108 | 109 | $(PREFIX_DIR)/prefixes.csv: $(PREFIX_DIR)/prefixes_curated.csv $(PREFIX_DIR)/prefixes_local.csv $(PREFIX_DIR)/obo_prefixes.csv 110 | cat $^ > $@ 111 | 112 | # see https://github.com/INCATools/relation-graph/issues/168 113 | $(PREFIX_YAML_PATH): $(PREFIX_CSV_PATH) 114 | grep -v ^prefix, $< | grep -v ^obo, | perl -npe 's@,(.*)@: "$$1"@' > $@.tmp && mv $@.tmp $@ 115 | 116 | 117 | 118 | # --- 119 | # sqlite db creation and loading 120 | # --- 121 | db/%.db: db/%.owl 122 | $(RUN) semsql make $@ 123 | .PRECIOUS: db/%.db 124 | 125 | statistics/%.statistics.yaml: db/%.db 126 | runoak -i $< statistics --group-by-prefix -o $@ 127 | 128 | validation/%.validate.tsv: db/%.db 129 | runoak -i $< validate -o $@ 130 | 131 | # --- 132 | # OBO Registry 133 | # --- 134 | # fetch list of ontologies from OBO registry 135 | 136 | 137 | # first fetch ontology list in rdf/owl; 138 | db/obo-ontologies.owl: 139 | robot convert -I http://purl.obolibrary.org/meta/ontologies.ttl -o $@ 140 | 141 | # depends on .db build of rdf/owl - then export to TSV 142 | # NOTE: currently there is a checked in copy of obo.tsv; use this, as we have pre-filtered ontologies that do not load 143 | reports/obo.tsv: db/obo-ontologies.db 144 | sqlite3 $< "SELECT subject FROM ontology_status_statement WHERE value = 'active'" | perl -npe 's@^obo:@@' > $@ 145 | 146 | 147 | 148 | 149 | # --- 150 | # Reports 151 | # --- 152 | reports/%.problems.tsv: db/%.db target/%.views 153 | sqlite3 $< "SELECT * FROM problems" > $@ 154 | 155 | 156 | 157 | # --- 158 | # Downloads 159 | # --- 160 | 161 | STAMP: 162 | touch $@ 163 | 164 | ## Additional ontologies 165 | 166 | src/semsql/builder/registry/registry_schema.py: src/semsql/builder/registry/registry_schema.yaml 167 | $(RUN) gen-python $< > $@ 168 | 169 | ontologies.Makefile: $(ONT_REGISTRY) 170 | $(RUN) semsql generate-makefile -P src/semsql/builder/prefixes/prefixes_local.csv $< > $@.tmp && mv $@.tmp $@ 171 | 172 | include ontologies.Makefile 173 | 174 | 175 | 176 | #fma.owl:# 177 | # http://purl.org/sig/ont/fma.owl 178 | 179 | 180 | # --- 181 | # Schema 182 | # --- 183 | # the section below here is only required for when changes to the schema is made; 184 | # changing the yaml triggers changes in 185 | # - derived SQL DDL 186 | # - SQL Alchemy objects 187 | # - Docs 188 | 189 | MODULES = rdf owl obo omo relation_graph semsql 190 | 191 | GENDOC_ARGS = -d docs --template-directory docgen-templates 192 | 193 | # TODO: markdown gen should make modular output 194 | markdown-%: $(YAML_DIR)/%.yaml 195 | $(RUN) gen-doc $(GENDOC_ARGS) $< && mv docs/index.md docs/$*_index.md 196 | markdown: $(patsubst %, markdown-%, $(MODULES)) 197 | $(RUN) gen-doc $(GENDOC_ARGS) $(YAML_DIR)/semsql.yaml 198 | 199 | gendoc: markdown 200 | 201 | gen-project: $(YAML_DIR)/semsql.yaml 202 | $(RUN) gen-project ${GEN_PARGS} $< -d project 203 | 204 | # Create SQL Create Table statements from linkml 205 | # 1. first use generic ddl generation 206 | # 2. Add views 207 | GENDDL = $(RUN) gen-sqlddl --dialect sqlite --no-use-foreign-keys 208 | gen-ddl: $(patsubst %, $(DDL_DIR)/%.sql, $(MODULES)) 209 | $(DDL_DIR)/%.sql: $(YAML_DIR)/%.yaml 210 | $(GENDDL) $< > $@.tmp && \ 211 | $(RUN) gen-semsql-views $< >> $@.tmp && \ 212 | mv $@.tmp $@ 213 | 214 | reports/query-%.sql: $(YAML_DIR)/%.yaml 215 | $(RUN) python src/semsql/sqlutils/reportgen.py $< > $@ 216 | 217 | # Generate SQL Alchemy 218 | gen-sqla: $(patsubst %, $(SQLA_DIR)/%.py, $(MODULES)) 219 | 220 | # make SQL Alchemy models 221 | # requires linkml 1.2.5 222 | $(SQLA_DIR)/%.py: $(YAML_DIR)/%.yaml 223 | $(RUN) gen-sqla --no-use-foreign-keys $< > $@ 224 | 225 | # -- makes bin/semsql -- 226 | # this is useful for core developers of semsql - 227 | # this will create a one line bash script that 228 | # can be added to your $PATH that will execute the 229 | # version of semsql used in github 230 | bin/%: 231 | echo `poetry run which $*` '$$*' > $@ && chmod +x $@ 232 | 233 | ### DEPLOY 234 | 235 | DATE = $(shell date -u +"%Y-%m-%d") 236 | 237 | s3-deploy: 238 | aws s3 sync stage s3://bbop-sqlite --acl public-read 239 | 240 | s3-version: 241 | aws s3 sync stage s3://bbop-sqlite/releases/$(DATE) --acl public-read 242 | 243 | s3-deploy-%: stage/%.db.gz 244 | aws s3 cp $< s3://bbop-sqlite/$*.db.gz --acl public-read 245 | 246 | 247 | # Test documentation locally 248 | serve: mkd-serve 249 | 250 | # Python datamodel 251 | $(PYMODEL): 252 | mkdir -p $@ 253 | 254 | 255 | $(DOCDIR): 256 | mkdir -p $@ 257 | 258 | testdoc: gendoc serve 259 | 260 | MKDOCS = $(RUN) mkdocs 261 | mkd-%: 262 | $(MKDOCS) $* 263 | 264 | ################################################ 265 | #### Commands for building the Docker image #### 266 | ################################################ 267 | 268 | IM=linkml/semantic-sql 269 | 270 | docker-build-no-cache: 271 | @docker build --no-cache -t $(IM):$(VERSION) . \ 272 | && docker tag $(IM):$(VERSION) $(IM):latest 273 | 274 | docker-build: 275 | @docker build -t $(IM):$(VERSION) . \ 276 | && docker tag $(IM):$(VERSION) $(IM):latest 277 | 278 | docker-build-use-cache-dev: 279 | @docker build -t $(DEV):$(VERSION) . \ 280 | && docker tag $(DEV):$(VERSION) $(DEV):latest 281 | 282 | docker-clean: 283 | docker kill $(IM) || echo not running ; 284 | docker rm $(IM) || echo not made 285 | 286 | docker-publish-no-build: 287 | @docker push $(IM):$(VERSION) \ 288 | && docker push $(IM):latest 289 | 290 | docker-publish-dev-no-build: 291 | @docker push $(DEV):$(VERSION) \ 292 | && docker push $(DEV):latest 293 | 294 | docker-publish: docker-build 295 | @docker push $(IM):$(VERSION) \ 296 | && docker push $(IM):latest 297 | 298 | docker-run: 299 | @docker run -v $(PWD):/work -w /work -ti $(IM):$(VERSION) 300 | 301 | test-docker-run: 302 | cd docker-test && docker run -v `pwd`/db:/semsql/db -w /semsql -ti $(IM):$(VERSION) make -k all RUN= 303 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SemSQL: standard SQL views for RDF/OWL ontologies 2 | 3 | [![PyPI version](https://badge.fury.io/py/semsql.svg)](https://badge.fury.io/py/semsql) 4 | ![](https://github.com/incatools/semantic-sql/workflows/Build/badge.svg) 5 | 6 | 7 | This project provides a standard collection of SQL tables/views for ontologies, such that you can make queries like this, 8 | to find all terms starting with `Abnormality` in [HPO](https://obofoundry.org/ontology/hp). 9 | 10 | ```sql 11 | $ sqlite db/hp.db 12 | sqlite> SELECT * FROM rdfs_label_statement WHERE value LIKE 'Abnormality of %'; 13 | ``` 14 | 15 | |stanza|subject|predicate|object|value|datatype|language| 16 | |---|---|---|---|---|---|---| 17 | |HP:0000002|HP:0000002|rdfs:label||Abnormality of body height|xsd:string|| 18 | |HP:0000014|HP:0000014|rdfs:label||Abnormality of the bladder|xsd:string|| 19 | |HP:0000022|HP:0000022|rdfs:label||Abnormality of male internal genitalia|xsd:string|| 20 | |HP:0000032|HP:0000032|rdfs:label||Abnormality of male external genitalia|xsd:string|| 21 | 22 | 23 | Ready-made SQLite3 builds can also be downloaded for any ontology in [OBO](http://obofoundry.org), using URLs such as https://s3.amazonaws.com/bbop-sqlite/hp.db.gz 24 | 25 | [relation-graph](https://github.com/balhoff/relation-graph/) is used to pre-generate tables of [entailed edges](https://incatools.github.io/semantic-sql/EntailedEdge/). For example, 26 | all is-a and part-of ancestors of [finger](http://purl.obolibrary.org/obo/UBERON_0002389) in Uberon: 27 | 28 | ```sql 29 | $ sqlite db/uberon.db 30 | sqlite> SELECT * FROM entailed_edge WHERE subject='UBERON:0002389' and predicate IN ('rdfs:subClassOf', 'BFO:0000050'); 31 | ``` 32 | 33 | |subject, predicate, object| 34 | |---| 35 | |UBERON:0002389, BFO:0000050, UBERON:0015212| 36 | |UBERON:0002389, BFO:0000050, UBERON:5002389| 37 | |UBERON:0002389, BFO:0000050, UBERON:5002544| 38 | |UBERON:0002389, rdfs:subClassOf, UBERON:0000061| 39 | |UBERON:0002389, rdfs:subClassOf, UBERON:0000465| 40 | |UBERON:0002389, rdfs:subClassOf, UBERON:0000475| 41 | 42 | SQLite provides many advantages 43 | 44 | - files can be downloaded and subsequently queried without network latency 45 | - compared to querying a static rdf, owl, or obo file, there is no startup/parse delay 46 | - robust and performant 47 | - excellent support in many languages 48 | 49 | Although the focus is on SQLite, this library can also be used for other DBMSs like PostgreSQL, MySQL, Oracle, etc 50 | 51 | ## Tutorials 52 | 53 | - SemSQL: [notebooks/SemanticSQL-Tutorial.ipynb](https://github.com/INCATools/semantic-sql/blob/main/notebooks/SemanticSQL-Tutorial.ipynb) 54 | - Using OAK: [part 7 of OAK tutorial](https://incatools.github.io/ontology-access-kit/intro/tutorial07.html) 55 | 56 | ## Installation 57 | 58 | SemSQL comes with a helper Python library. Use of this is optional. To install: 59 | 60 | ```bash 61 | pip install semsql 62 | ``` 63 | 64 | ## Download ready-made SQLite databases 65 | 66 | Pre-generated SQLite database are created weekly for all OBO ontologies and a selection of others (see [ontologies.yaml](https://github.com/INCATools/semantic-sql/blob/main/src/semsql/builder/registry/ontologies.yaml)) 67 | 68 | To download: 69 | 70 | ```bash 71 | semsql download obi -o obi.db 72 | ``` 73 | 74 | Or simply download using URL of the form: 75 | 76 | - https://s3.amazonaws.com/bbop-sqlite/hp.db.gz 77 | 78 | ## Attaching databases 79 | 80 | If you are using sqlite3, then databases can be attached to facilitate cross-database joins. 81 | 82 | For example, many ontologies use ORCID URIs as the object of `dcterms:contributor` and `dcterms:creator` statements, but these are left "dangling". Metadata about these orcids are available in the semsql orcid database instance (derived from [wikidata-orcid-ontology](https://github.com/cthoyt/wikidata-orcid-ontology)), in the [Orcid table](https://incatools.github.io/semantic-sql/Orcid). 83 | 84 | You can use [ATTACH DATABASE](https://www.sqlite.org/lang_attach.html) to connect two databases, for example: 85 | 86 | ```sql 87 | $ sqlite3 db/cl.dl 88 | sqlite> attach 'db/orcid.db' as orcid_db; 89 | sqlite> select * from contributor inner join orcid_db.orcid on (orcid.id=contributor.object) where orcid.label like 'Chris%'; 90 | obo:cl.owl|obo:cl.owl|dcterms:contributor|orcid:0000-0002-6601-2165||||orcid:0000-0002-6601-2165|Christopher J. Mungall 91 | CL:0010001|CL:0010001|dcterms:contributor|orcid:0000-0002-6601-2165||||orcid:0000-0002-6601-2165|Christopher J. Mungall 92 | CL:0010002|CL:0010002|dcterms:contributor|orcid:0000-0002-6601-2165||||orcid:0000-0002-6601-2165|Christopher J. Mungall 93 | CL:0010003|CL:0010003|dcterms:contributor|orcid:0000-0002-6601-2165||||orcid:0000-0002-6601-2165|Christopher J. Mungall 94 | CL:0010004|CL:0010004|dcterms:contributor|orcid:0000-0002-6601-2165||||orcid:0000-0002-6601-2165|Christopher J. Mungall 95 | UBERON:0000093|UBERON:0000093|dcterms:contributor|orcid:0000-0002-6601-2165||||orcid:0000-0002-6601-2165|Christopher J. Mungall 96 | UBERON:0000094|UBERON:0000094|dcterms:contributor|orcid:0000-0002-6601-2165||||orcid:0000-0002-6601-2165|Christopher J. Mungall 97 | UBERON:0000095|UBERON:0000095|dcterms:contributor|orcid:0000-0002-6601-2165||||orcid:0000-0002-6601-2165|Christopher J. Mungall 98 | UBERON:0000179|UBERON:0000179|dcterms:contributor|orcid:0000-0002-6601-2165||||orcid:0000-0002-6601-2165|Christopher J. Mungall 99 | UBERON:0000201|UBERON:0000201|dcterms:contributor|orcid:0000-0002-6601-2165||||orcid:0000-0002-6601-2165|Christopher J. Mungall 100 | UBERON:0000202|UBERON:0000202|dcterms:contributor|orcid:0000-0002-6601-2165||||orcid:0000-0002-6601-2165|Christopher J. Mungall 101 | UBERON:0000203|UBERON:0000203|dcterms:contributor|orcid:0000-0002-6601-2165||||orcid:0000-0002-6601-2165|Christopher J. Mungall 102 | UBERON:0000204|UBERON:0000204|dcterms:contributor|orcid:0000-0002-6601-2165||||orcid:0000-0002-6601-2165|Christopher J. Mungall 103 | ``` 104 | 105 | ## Creating a SQLite database from an OWL file 106 | 107 | There are two protocols for doing this: 108 | 109 | 1. install build dependencies 110 | 2. use Docker 111 | 112 | In either case: 113 | 114 | - The input MUST be in RDF/XML serialization and have the suffix `.owl`: 115 | - use robot to convert if format is different 116 | 117 | We are planning to simplify this process in future. 118 | 119 | ### 1. Build a SQLite database directly 120 | 121 | This requires some basic technical knowledge about how to install things on your machine 122 | and how to put things in your PATH. It does not require Docker. 123 | 124 | Requirements: 125 | 126 | - [rdftab.rs](https://github.com/ontodev/rdftab.rs) 127 | - [relation-graph](https://github.com/balhoff/relation-graph) `2.3.1` or higher 128 | 129 | After installing these and putting both `relation-graph` and `rdftab.rs` in your path: 130 | 131 | ```bash 132 | semsql make foo.db 133 | ``` 134 | 135 | This assumes `foo.owl` is in the same folder 136 | 137 | ### 2. Use Docker 138 | 139 | There are two docker images that can be used: 140 | 141 | - [ODK](https://hub.docker.com/r/obolibrary/odkfull) 142 | - [semantic-sql](https://hub.docker.com/repository/docker/linkml/semantic-sql) 143 | 144 | The ODK image may lag behind 145 | 146 | ```bash 147 | docker run -v $PWD:/work -w /work -ti linkml/semantic-sql semsql make foo.db 148 | ``` 149 | 150 | ## Schema 151 | 152 | See [Schema Documentation](https://incatools.github.io/semantic-sql/) 153 | 154 | The [source schema](https://github.com/INCATools/semantic-sql/tree/main/src/semsql/linkml) is in [LinkML](https://linkml.io) - this is then compiled down to SQL Tables and Views 155 | 156 | The basic idea is as follows: 157 | 158 | There are a small number of "base tables": 159 | 160 | * [statements](https://incatools.github.io/semantic-sql/Statements/) 161 | * [prefix](https://incatools.github.io/semantic-sql/Prefix/) 162 | * [entailed_edge](https://incatools.github.io/semantic-sql/EntailedEdge/) - populated by relation-graph 163 | 164 | All other tables are actually views (derived tables), and are provided for convenience. 165 | 166 | ## ORM Layer 167 | 168 | A SemSQL relational database can be accessed in exactly the same way as any other SQLdb 169 | 170 | For convenience, we provide a Python Object-Relational Mapping (ORM) layer using SQL Alchemy. 171 | This allows for code uchlike the following, which joins [RdfsSubclassOfStatement](https://incatools.github.io/semantic-sql/RdfsSubclassOfStatement) and [existential restrictions](https://incatools.github.io/semantic-sql/OwlSomeValuesFrom): 172 | 173 | ```python 174 | engine = create_engine(f"sqlite:////path/to/go.db") 175 | SessionClass = sessionmaker(bind=engine) 176 | session = SessionClass() 177 | q = session.query(RdfsSubclassOfStatement) 178 | q = q.add_entity(OwlSomeValuesFrom) 179 | q = q.join(OwlSomeValuesFrom, RdfsSubclassOfStatement.object == OwlSomeValuesFrom.id) 180 | 181 | lines = [] 182 | for ax, ex in q.all(): 183 | line = f'{ax.subject} subClassOf {ex.on_property} SOME {ex.filler}' 184 | logging.info(line) 185 | lines.append(line) 186 | ``` 187 | 188 | (this example is just for illustration - to do the same thing there is a simpler Edge relation) 189 | 190 | ## Applications 191 | 192 | The semsql python library is intentionally low level - we recommend using the [ontology-access-kit](https://github.com/INCATools/ontology-access-kit) 193 | 194 | For example: 195 | 196 | ```bash 197 | runoak -i db/envo.db search t~biome 198 | ``` 199 | 200 | You can also pass in an OWL file and have the sqlite be made on the fly 201 | 202 | ```bash 203 | runoak -i sqlite:envo.owl search t~biome 204 | ``` 205 | 206 | Even if using OAK, it can be useful to access SQL tables directly to do complex multi-join queries in a performant way. 207 | 208 | ## Optimization 209 | 210 | ```bash 211 | poetry run semsql view2table edge --full-index | sqlite3 $db/mydb.db 212 | ``` 213 | 214 | See [indexes](indexes) for some ready-made indexes 215 | -------------------------------------------------------------------------------- /bin/README.txt: -------------------------------------------------------------------------------- 1 | . -------------------------------------------------------------------------------- /conf/obograph-style.json: -------------------------------------------------------------------------------- 1 | { 2 | "styles": [ 3 | "filled", 4 | "rounded" 5 | ], 6 | "fillcolor": "white", 7 | 8 | "excludeSingletons": "true", 9 | "relationProperties": { 10 | "rdfs:subClassOf": { 11 | "color": "black", 12 | "penwidth": 3, 13 | "arrowhead": "open", 14 | "label": "" 15 | }, 16 | "rdf:type": { 17 | "color": "blue", 18 | "arrowhead": "open", 19 | "label": "type" 20 | }, 21 | "RO:0002473": { 22 | "color": "gray", 23 | "arrowhead": "box", 24 | "label": "composed primarily of" 25 | }, 26 | "RO:0002220": { 27 | "color": "gray", 28 | "label": "A" 29 | }, 30 | "RO:0001025": { 31 | "arrowhead": "diamond", 32 | "color": "green", 33 | "penwidth": 2, 34 | "label": "L" 35 | }, 36 | "RO:0002202": { 37 | "color": "green", 38 | "label": "DF" 39 | }, 40 | "BFO:0000050": { 41 | "arrowhead": "tee", 42 | "color": "blue", 43 | "penwidth": 3, 44 | "label": "" 45 | } 46 | }, 47 | "conditionalProperties": [ 48 | { 49 | "conditions": { 50 | "type": "INSTANCE" 51 | }, 52 | "properties": { 53 | "style": "filled,square", 54 | "fillcolor": "green" 55 | } 56 | } 57 | ], 58 | "displayAnnotations": [ 59 | "wgs84:lat", "wgs84:long", "sosa:hasSimpleResult" 60 | ], 61 | "prefixProperties": { 62 | "UBERON": { 63 | "fillcolor": "yellow" 64 | }, 65 | "CL": { 66 | "fillcolor": "azure2" 67 | }, 68 | "GO": { 69 | "fillcolor": "pink" 70 | }, 71 | "MBA": { 72 | "fillcolor": "hotpink" 73 | }, 74 | "HBA": { 75 | "fillcolor": "lightgrey" 76 | }, 77 | "ZFA": { 78 | "fillcolor": "black", 79 | "fontcolor": "white" 80 | }, 81 | "RO": { 82 | "fillcolor": "pink" 83 | }, 84 | "PATO": { 85 | "fillcolor": "pink" 86 | }, 87 | "ENVO": { 88 | "fillcolor": "cyan" 89 | }, 90 | "NCBITaxon": { 91 | "fillcolor": "burlywood1" 92 | }, 93 | "FOODON": { 94 | "fillcolor": "pink" 95 | }, 96 | "http://purl.obolibrary.org/obo/CHEBI_": { 97 | "fillcolor": "pink" 98 | }, 99 | "http://purl.obolibrary.org/obo/BFO_": { 100 | "fillcolor": "cyan" 101 | } 102 | }, 103 | "cliqueRelations": [ 104 | "equivalent_to", "same_as", "xref" 105 | ] 106 | } 107 | -------------------------------------------------------------------------------- /config.env: -------------------------------------------------------------------------------- 1 | LINKML_GENERATORS_PROJECT_ARGS=--config-file config.yaml 2 | 3 | ### Extra layer of configuration for Makefile beyond 4 | ### what 'gen-project --config-file config.yaml' provides. 5 | ### Uncomment and set environment variables as needed. 6 | 7 | # LINKML_GENERATORS_MARKDOWN_ARGS=--no-mergeimports 8 | 9 | -------------------------------------------------------------------------------- /db/README.md: -------------------------------------------------------------------------------- 1 | # sqlite dbs go here 2 | -------------------------------------------------------------------------------- /demo/cam/README.md: -------------------------------------------------------------------------------- 1 | Downloaded files will go here 2 | -------------------------------------------------------------------------------- /docgen-templates/class.md.jinja2: -------------------------------------------------------------------------------- 1 | {%- if element.title %} 2 | {%- set title = element.title ~ ' (' ~ element.name ~ ')' -%} 3 | {%- else %} 4 | {%- if gen.use_class_uris -%} 5 | {%- set title = element.name -%} 6 | {%- else -%} 7 | {%- set title = gen.name(element) -%} 8 | {%- endif -%} 9 | {%- endif -%} 10 | 11 | {% macro compute_range(slot) -%} 12 | {%- if slot.any_of or slot.exactly_one_of -%} 13 | {%- for subslot_range in schemaview.slot_range_as_union(slot) -%} 14 | {{ gen.link(subslot_range) }} 15 | {%- if not loop.last -%} 16 |  or 
17 | {%- endif -%} 18 | {%- endfor -%} 19 | {%- else -%} 20 | {{ gen.link(slot.range) }} 21 | {%- endif -%} 22 | {% endmacro %} 23 | 24 | # Class: {{ title }} 25 | 26 | {%- if header -%} 27 | {{header}} 28 | {%- endif -%} 29 | 30 | 31 | {% if element.description %} 32 | {% set element_description_lines = element.description.split('\n') %} 33 | {% for element_description_line in element_description_lines %} 34 | _{{ element_description_line }}_ 35 | {% endfor %} 36 | {% endif %} 37 | 38 | {% if element.abstract %} 39 | * __NOTE__: this is an abstract class and should not be instantiated directly 40 | {% endif %} 41 | 42 | URI: {{ gen.uri_link(element) }} 43 | 44 | 45 | {% if diagram_type == "er_diagram" %} 46 | ```{{ gen.mermaid_directive() }} 47 | {{ gen.mermaid_diagram([element.name]) }} 48 | ``` 49 | {% elif diagram_type == "plantuml_class_diagram" %} 50 | ```puml 51 | {{ gen.mermaid_diagram([element.name]) }} 52 | ``` 53 | {% else %} 54 | {% include "class_diagram.md.jinja2" %} 55 | {% endif %} 56 | 57 | {% if schemaview.class_parents(element.name) or schemaview.class_children(element.name, mixins=False) %} 58 | 59 | ## Usage 60 | 61 | ```sql 62 | SELECT * FROM {{element.name}}; 63 | ``` 64 | 65 | ## Inheritance 66 | {{ gen.inheritance_tree(element, mixins=True) }} 67 | {% else %} 68 | 69 | {% endif %} 70 | 71 | ## Slots 72 | 73 | | Name | Cardinality and Range | Description | Inheritance | 74 | | --- | --- | --- | --- | 75 | {% if gen.get_direct_slots(element)|length > 0 %} 76 | {%- for slot in gen.get_direct_slots(element) -%} 77 | | {{ gen.link(slot) }} | {{ gen.cardinality(slot) }}
{{ compute_range(slot) }} | {{ slot.description|enshorten }} | direct | 78 | {% endfor -%} 79 | {% endif -%} 80 | {% if gen.get_indirect_slots(element)|length > 0 %} 81 | {%- for slot in gen.get_indirect_slots(element) -%} 82 | | {{ gen.link(slot) }} | {{ gen.cardinality(slot) }}
{{ compute_range(slot) }} | {{ slot.description|enshorten }} | {{ gen.links(gen.get_slot_inherited_from(element.name, slot.name))|join(', ') }} | 83 | {% endfor -%} 84 | {% endif %} 85 | 86 | {% if schemaview.is_mixin(element.name) %} 87 | ## Mixin Usage 88 | 89 | | mixed into | description | 90 | | --- | --- | 91 | {% for c in schemaview.class_children(element.name, is_a=False) -%} 92 | | {{ gen.link(c) }} | {{ schemaview.get_class(c).description|enshorten }} | 93 | {% endfor %} 94 | {% endif %} 95 | 96 | {% if schemaview.usage_index().get(element.name) %} 97 | ## Usages 98 | 99 | | used by | used in | type | used | 100 | | --- | --- | --- | --- | 101 | {% for usage in schemaview.usage_index().get(element.name) -%} 102 | | {{gen.link(usage.used_by)}} | {{gen.link(usage.slot)}} | {{usage.metaslot}} | {{ gen.link(usage.used) }} | 103 | {% endfor %} 104 | {% endif %} 105 | 106 | {% include "common_metadata.md.jinja2" %} 107 | 108 | {% if element.classification_rules %} 109 | ## Classification Rules 110 | 111 | This class has classification rules. This allows this class (table) to be derived 112 | from another class (table) via a query. 113 | 114 | {% for r in element.classification_rules %} 115 | * Parent: {{gen.link(r.is_a)}} 116 | * Conditions: 117 | {% for sc in r.slot_conditions.values() %} 118 | * {{gen.link(sc.name)}} = {{sc.equals_string}} 119 | {% endfor %} 120 | {% endfor %} 121 | 122 | {% endif %} 123 | 124 | {% if element.rules or element.classification_rules %} 125 | 126 | ## Rules 127 | 128 | {% endif %} 129 | 130 | {% for comment in element.comments %} 131 | {% if comment.startswith("sqlview>>") %} 132 | ## SQL View 133 | 134 | This class has a SQL view definition: 135 | 136 | ``` 137 | {{comment}} 138 | ``` 139 | 140 | {% endif %} 141 | {% endfor %} 142 | 143 | {% if schemaview.get_mappings(element.name).items() -%} 144 | ## Mappings 145 | 146 | | Mapping Type | Mapped Value | 147 | | --- | --- | 148 | {% for m, mt in schemaview.get_mappings(element.name).items() -%} 149 | {% if mt|length > 0 -%} 150 | | {{ m }} | {{ mt|join(', ') }} | 151 | {% endif -%} 152 | {% endfor %} 153 | 154 | {% endif -%} 155 | 156 | {% if gen.example_object_blobs(element.name) -%} 157 | ## Examples 158 | {% for name, blob in gen.example_object_blobs(element.name) -%} 159 | ### Example: {{name}} 160 | 161 | ```yaml 162 | {{ blob }} 163 | ``` 164 | {% endfor %} 165 | {% endif %} 166 | 167 | 168 | ## LinkML Source 169 | 170 | 171 | 172 | ### Direct 173 | 174 |
175 | ```yaml 176 | {{gen.yaml(element)}} 177 | ``` 178 |
179 | 180 | ### Induced 181 | 182 |
183 | ```yaml 184 | {{gen.yaml(element, inferred=True)}} 185 | ``` 186 |
187 | 188 | {%- if footer -%} 189 | {{footer}} 190 | {%- endif -%} 191 | -------------------------------------------------------------------------------- /docgen-templates/common_metadata.md.jinja2: -------------------------------------------------------------------------------- 1 | {% if element.aliases %} 2 | ## Aliases 3 | 4 | {% for alias in element.aliases %} 5 | * {{ alias }} 6 | {%- endfor %} 7 | {% endif %} 8 | 9 | 10 | {% if element.examples %} 11 | ## Examples 12 | 13 | | Value | 14 | | --- | 15 | {% for x in element.examples -%} 16 | | {{ x.value }} | 17 | {% endfor %} 18 | {% endif -%} 19 | 20 | {% if element.comments -%} 21 | ## Comments 22 | 23 | {% for x in element.comments -%} 24 | * {{x}} 25 | {% endfor %} 26 | {% endif -%} 27 | 28 | {% if element.todos -%} 29 | ## TODOs 30 | 31 | {% for x in element.todos -%} 32 | * {{x}} 33 | {% endfor %} 34 | {% endif -%} 35 | 36 | {% if element.see_also -%} 37 | ## See Also 38 | 39 | {% for x in element.see_also -%} 40 | * {{ gen.uri_link(x) }} 41 | {% endfor %} 42 | {% endif -%} 43 | 44 | ## Identifier and Mapping Information 45 | 46 | {% if element.id_prefixes %} 47 | ### Valid ID Prefixes 48 | 49 | Instances of this class *should* have identifiers with one of the following prefixes: 50 | {% for p in element.id_prefixes %} 51 | * {{p}} 52 | {% endfor %} 53 | 54 | {% endif %} 55 | 56 | 57 | {% if element.annotations %} 58 | ### Annotations 59 | 60 | | property | value | 61 | | --- | --- | 62 | {% for a in element.annotations -%} 63 | {%- if a|string|first != '_' -%} 64 | | {{ a }} | {{ element.annotations[a].value }} | 65 | {%- endif -%} 66 | {% endfor %} 67 | {% endif %} 68 | 69 | {% if element.from_schema or element.imported_from %} 70 | ### Schema Source 71 | 72 | {% if element.from_schema %} 73 | * from schema: {{ element.from_schema }} 74 | {% endif %} 75 | {% if element.imported_from %} 76 | * imported from: {{ element.imported_from }} 77 | {% endif %} 78 | {% endif %} -------------------------------------------------------------------------------- /docgen-templates/enum.md.jinja2: -------------------------------------------------------------------------------- 1 | # Enum: {{ gen.name(element) }} 2 | 3 | {% if element.description %} 4 | {% set element_description_lines = element.description.split('\n') %} 5 | {% for element_description_line in element_description_lines %} 6 | _{{ element_description_line }}_ 7 | {% endfor %} 8 | {% endif %} 9 | 10 | URI: {{ gen.link(element) }} 11 | 12 | {% if element.permissible_values -%} 13 | ## Permissible Values 14 | 15 | | Value | Meaning | Description | 16 | | --- | --- | --- | 17 | {% for pv in element.permissible_values.values() -%} 18 | | {{pv.text}} | {{pv.meaning}} | {{pv.description|enshorten}} | 19 | {% endfor %} 20 | {% else %} 21 | _This is a dynamic enum_ 22 | {% endif %} 23 | 24 | {% set slots_for_enum = schemaview.get_slots_by_enum(element.name) %} 25 | {% if slots_for_enum is defined and slots_for_enum|length > 0 -%} 26 | ## Slots 27 | 28 | | Name | Description | 29 | | --- | --- | 30 | {% for s in schemaview.get_slots_by_enum(element.name) -%} 31 | | {{gen.link(s)}} | {{s.description|enshorten}} | 32 | {% endfor %} 33 | {% endif %} 34 | 35 | {% include "common_metadata.md.jinja2" %} 36 | 37 | ## LinkML Source 38 | 39 |
40 | ```yaml 41 | {{gen.yaml(element)}} 42 | ``` 43 |
44 | 45 | -------------------------------------------------------------------------------- /docgen-templates/index.md.jinja2: -------------------------------------------------------------------------------- 1 | # {% if schema.title %}{{ schema.title }}{% else %}{{ schema.name }}{% endif %} 2 | 3 | {% if schema.description %}{{ schema.description }}{% endif %} 4 | 5 | URI: {{ schema.id }} 6 | 7 | Name: {{ schema.name }} 8 | 9 | {% if include_top_level_diagram %} 10 | 11 | ## Schema Diagram 12 | 13 | ```{{ gen.mermaid_directive() }} 14 | {{ gen.mermaid_diagram() }} 15 | ``` 16 | {% endif %} 17 | 18 | ## Classes 19 | 20 | | Class | Description | 21 | | --- | --- | 22 | {% if gen.hierarchical_class_view -%} 23 | {% for u, v in gen.class_hierarchy_as_tuples() -%} 24 | {%- set link = gen.link(schemaview.get_class(v)) -%} 25 | {%- set hi = "**" if schemaview.get_class(v, imports=False) else "*" -%} 26 | | {{ " "|safe*u*8 }}{{ hi }}{{ link }}{{ hi }} | {{ schemaview.get_class(v).description }} | 27 | {% endfor %} 28 | {% else -%} 29 | {% for c in gen.all_class_objects()|sort(attribute=sort_by) -%} 30 | | {{gen.link(c)}} | {{c.description|enshorten}} | 31 | {% endfor %} 32 | {% endif %} 33 | 34 | ## Slots 35 | 36 | | Slot | Description | 37 | | --- | --- | 38 | {% for s in gen.all_slot_objects()|sort(attribute=sort_by) -%} 39 | | {{gen.link(s)}} | {{s.description|enshorten}} | 40 | {% endfor %} 41 | 42 | ## Enumerations 43 | 44 | | Enumeration | Description | 45 | | --- | --- | 46 | {% for e in gen.all_enum_objects()|sort(attribute=sort_by) -%} 47 | | {{gen.link(e)}} | {{e.description|enshorten}} | 48 | {% endfor %} 49 | 50 | ## Types 51 | 52 | | Type | Description | 53 | | --- | --- | 54 | {% for t in gen.all_type_objects()|sort(attribute=sort_by) -%} 55 | | {{gen.link(t)}} | {{t.description|enshorten}} | 56 | {% endfor %} 57 | 58 | ## Subsets 59 | 60 | | Subset | Description | 61 | | --- | --- | 62 | {% for ss in schemaview.all_subsets().values()|sort(attribute='name') -%} 63 | | {{gen.link(ss)}} | {{ss.description|enshorten}} | 64 | {% endfor %} 65 | -------------------------------------------------------------------------------- /docgen-templates/index.tex.jinja2: -------------------------------------------------------------------------------- 1 | \documentclass{article} 2 | \usepackage[utf8]{inputenc} 3 | 4 | \title{ {{gen.latex(gen.schema_title())}} } 5 | 6 | \begin{document} 7 | 8 | {{ schema.description }} 9 | 10 | URI: {{ gen.latex(schema.id) }} 11 | Name: {{ gen.latex(schema.name) }} 12 | 13 | \subsection{Classes}\label{classes} 14 | 15 | \begin{tabular}{ |c|c| } 16 | \hline 17 | Class & Description \\\\ 18 | \hline 19 | 20 | {% for c in schemaview.all_classes().values()|sort(attribute='name') -%} 21 | {{ gen.latex(c.name) }} & {{ gen.latex(c.description) }} \\\\ 22 | {% endfor %} 23 | \hline 24 | \end{tabular} 25 | 26 | \end{document} 27 | -------------------------------------------------------------------------------- /docgen-templates/schema.md.jinja2: -------------------------------------------------------------------------------- 1 | # {{ schema.name }} 2 | 3 | {{ schema.description }} 4 | 5 | URI: {{ schema.id }} 6 | 7 | 8 | -------------------------------------------------------------------------------- /docgen-templates/slot.md.jinja2: -------------------------------------------------------------------------------- 1 | {%- if element.title %} 2 | {%- set title = element.title ~ ' (' ~ element.name ~ ')' -%} 3 | {%- else %} 4 | {%- if gen.use_slot_uris -%} 5 | {%- set title = element.name -%} 6 | {%- else -%} 7 | {%- set title = gen.name(element) -%} 8 | {%- endif -%} 9 | {%- endif -%} 10 | 11 | {% macro compute_range(slot) -%} 12 | {%- if slot.any_of or slot.exactly_one_of -%} 13 | {%- for subslot_range in schemaview.slot_range_as_union(slot) -%} 14 | {{ gen.link(subslot_range) }} 15 | {%- if not loop.last -%} 16 |  or 
17 | {%- endif -%} 18 | {%- endfor -%} 19 | {%- else -%} 20 | {{ gen.link(slot.range) }} 21 | {%- endif -%} 22 | {% endmacro %} 23 | 24 | # Slot: {{ title }} 25 | 26 | {%- if header -%} 27 | {{header}} 28 | {%- endif -%} 29 | 30 | {% if element.description %} 31 | {% set element_description_lines = element.description.split('\n') %} 32 | {% for element_description_line in element_description_lines %} 33 | _{{ element_description_line }}_ 34 | {% endfor %} 35 | {% endif %} 36 | 37 | URI: {{ gen.uri_link(element) }} 38 | 39 | 40 | {% if schemaview.slot_parents(element.name) or schemaview.slot_children(element.name, mixins=False) %} 41 | 42 | ## Inheritance 43 | 44 | {{ gen.inheritance_tree(element, mixins=True) }} 45 | {% else %} 46 | 47 | {% endif %} 48 | 49 | {% set classes_by_slot = schemaview.get_classes_by_slot(element, include_induced=True) %} 50 | {% if classes_by_slot %} 51 | 52 | ## Applicable Classes 53 | 54 | | Name | Description | Modifies Slot | 55 | | --- | --- | --- | 56 | {% for c in classes_by_slot -%} 57 | | {{ gen.link(c) }} | {{ schemaview.get_class(c).description|enshorten }} | {% if c in schemaview.get_classes_modifying_slot(element) %} yes {% else %} no {% endif %} | 58 | {% endfor %} 59 | 60 | {% endif %} 61 | 62 | 63 | {% if schemaview.is_mixin(element.name) %} 64 | ## Mixin Usage 65 | 66 | | mixed into | description | range | domain | 67 | | --- | --- | --- | --- | 68 | {% for s in schemaview.slot_children(element.name, is_a=False) -%} 69 | | {{ gen.link(s) }} | {{ schemaview.get_slot(s).description|enshorten }} | {{ schemaview.get_slot(s).range }} | {{ schemaview.get_classes_by_slot(schemaview.get_slot(s))|join(', ') }} | 70 | {% endfor %} 71 | {% endif %} 72 | 73 | ## Properties 74 | 75 | * Range: {{ compute_range(element) }} 76 | {% if element.multivalued %} 77 | * Multivalued: {{ element.multivalued }} 78 | {% endif -%} 79 | {% if element.required %} 80 | * Required: {{ element.required }} 81 | {% elif element.recommended %} 82 | * Recommended: {{ element.recommended }} 83 | {% endif -%} 84 | {% if element.minimum_value is not none %} 85 | * Minimum Value: {{ element.minimum_value|int }} 86 | {% endif -%} 87 | {% if element.maximum_value is not none %} 88 | * Maximum Value: {{ element.maximum_value|int }} 89 | {% endif -%} 90 | {% if element.pattern %} 91 | * Regex pattern: {{ '`' }}{{ element.pattern }}{{ '`' }} 92 | {% endif -%} 93 | {% if schemaview.is_mixin(element.name) %} 94 | * Mixin: {{ element.mixin }} 95 | {% endif -%} 96 | 97 | 98 | {% if schemaview.usage_index().get(element.name) %} 99 | ## Usages 100 | 101 | | used by | used in | type | used | 102 | | --- | --- | --- | --- | 103 | {% for usage in schemaview.usage_index().get(element.name) -%} 104 | | {{gen.link(usage.used_by)}} | {{gen.link(usage.slot)}} | {{usage.metaslot}} | {{ gen.link(usage.used) }} | 105 | {% endfor %} 106 | {% endif %} 107 | 108 | {% include "common_metadata.md.jinja2" %} 109 | 110 | ## LinkML Source 111 | 112 |
113 | ```yaml 114 | {{ gen.yaml(element) }} 115 | ``` 116 |
117 | 118 | {%- if footer -%} 119 | {{footer}} 120 | {%- endif -%} -------------------------------------------------------------------------------- /docgen-templates/subset.md.jinja2: -------------------------------------------------------------------------------- 1 | # Subset: {{ gen.name(element) }} 2 | 3 | {%- if header -%} 4 | {{ header }} 5 | {%- endif -%} 6 | 7 | {% if element.description %} 8 | {% set element_description_lines = element.description.split('\n') %} 9 | {% for element_description_line in element_description_lines %} 10 | _{{ element_description_line }}_ 11 | {% endfor %} 12 | {% endif %} 13 | 14 | URI: {{ gen.link(element) }} 15 | 16 | {% include "common_metadata.md.jinja2" %} 17 | 18 | {% set classes_in_subset = [] %} 19 | {% set slots_in_subset = [] %} 20 | {% set enums_in_subset = [] %} 21 | 22 | {# Collect classes, slots, and enumerations in subset #} 23 | {% for c in gen.all_class_objects()|sort(attribute=sort_by) %} 24 | {%- if element.name in c.in_subset %} 25 | {% set _ = classes_in_subset.append(c) %} 26 | {%- endif %} 27 | {% endfor %} 28 | 29 | {% for s in gen.all_slot_objects()|sort(attribute=sort_by) %} 30 | {%- if element.name in s.in_subset %} 31 | {% set _ = slots_in_subset.append(s) %} 32 | {%- endif %} 33 | {% endfor %} 34 | 35 | {% for e in schemaview.all_enums().values() %} 36 | {%- if element.name in e.in_subset %} 37 | {% set _ = enums_in_subset.append(e) %} 38 | {%- endif %} 39 | {% endfor %} 40 | 41 | {% if classes_in_subset %} 42 | ## Classes in subset 43 | 44 | | Class | Description | 45 | | --- | --- | 46 | {% for c in gen.all_class_objects()|sort(attribute=sort_by) -%} 47 | {%- if element.name in c.in_subset -%} 48 | | {{gen.link(c)}} | {{c.description|enshorten}} | 49 | {% endif -%} 50 | {% endfor %} 51 | 52 | {% for c in gen.all_class_objects()|sort(attribute=sort_by) -%} 53 | {%- if element.name in c.in_subset -%} 54 | ### {{ gen.name(c) }} 55 | 56 | {{c.description}} 57 | 58 | {%- set filtered_slots = [] -%} 59 | 60 | {%- for s in induced_slots|sort(attribute=sort_by) -%} 61 | {%- if element.name in s.in_subset or element.name in schemaview.get_slot(s.name).in_subset -%} 62 | {% set _ = filtered_slots.append(s) %} 63 | {%- endif -%} 64 | {%- endfor %} 65 | 66 | {%- if filtered_slots|length > 0 -%} 67 | | Name | Cardinality and Range | Description | 68 | | --- | --- | --- | 69 | {% for s in filtered_slots -%} 70 | | {{gen.link(s)}} | {{ gen.cardinality(s) }}
{{gen.link(s.range)}} | {{s.description|enshorten}} {% if s.identifier %}**identifier**{% endif %} | 71 | {% endfor %} 72 | {%- endif %} 73 | 74 | 75 | {%- endif %} 76 | {% endfor %} 77 | 78 | {%- endif %} 79 | 80 | 81 | {% if slots_in_subset %} 82 | ## Slots in subset 83 | 84 | | Slot | Description | 85 | | --- | --- | 86 | {% for s in slots_in_subset|sort(attribute=sort_by) -%} 87 | {%- if element.name in s.in_subset -%} 88 | | {{ gen.link(s) }} | {{ s.description|enshorten }} | 89 | {%- endif %} 90 | {% endfor %} 91 | 92 | {%- endif %} 93 | 94 | 95 | {% if enums_in_subset %} 96 | ## Enumerations in subset 97 | 98 | | Enumeration | Description | 99 | | --- | --- | 100 | {% for e in enums_in_subset|sort(attribute='name') -%} 101 | {%- if element.name in e.in_subset %} 102 | | {{ gen.link(e) }} | {{ e.description|enshorten }} | 103 | {%- endif %} 104 | {% endfor %} 105 | 106 | {%- endif %} 107 | -------------------------------------------------------------------------------- /docgen-templates/type.md.jinja2: -------------------------------------------------------------------------------- 1 | # Type: {{ gen.name(element) }} 2 | 3 | {% if element.description %} 4 | {% set element_description_lines = element.description.split('\n') %} 5 | {% for element_description_line in element_description_lines %} 6 | _{{ element_description_line }}_ 7 | {% endfor %} 8 | {% endif %} 9 | 10 | URI: {{ gen.uri_link(element) }} 11 | 12 | {{ gen.bullet(element, "base") }} 13 | {{ gen.bullet(element, "uri") }} 14 | {{ gen.bullet(element, "repr") }} 15 | {{ gen.bullet(element, "typeof") }} 16 | {{ gen.bullet(element, "pattern", backquote=True) }} 17 | {% if gen.number_value_range(element) %} 18 | * Numeric Value Range: {{gen.number_value_range(element)}} 19 | {% endif %} 20 | 21 | {% include "common_metadata.md.jinja2" %} 22 | 23 | -------------------------------------------------------------------------------- /indexes/all-indexes.sql: -------------------------------------------------------------------------------- 1 | CREATE INDEX statements_spo ON statements(subject,predicate,object); 2 | CREATE INDEX statements_sp ON statements(subject,predicate); 3 | CREATE INDEX statements_spv ON statements(subject,predicate,value); 4 | CREATE INDEX statements_p ON statements(predicate); 5 | 6 | CREATE INDEX entailed_edge_spo on entailed_edge(subject, predicate, object); 7 | CREATE INDEX entailed_edge_sp on entailed_edge(subject, predicate); 8 | -------------------------------------------------------------------------------- /install.Makefile: -------------------------------------------------------------------------------- 1 | # --- 2 | ### RDFTab 3 | # --- 4 | # from https://github.com/obi-ontology/obi/blob/master/Makefile#L49 5 | # 6 | # Use RDFTab to create SQLite databases from OWL files. 7 | UNAME := $(shell uname) 8 | ifeq ($(UNAME), Darwin) 9 | RDFTAB_URL := https://github.com/ontodev/rdftab.rs/releases/download/v0.1.1/rdftab-x86_64-apple-darwin 10 | SED = sed -i.bak 11 | else 12 | RDFTAB_URL := https://github.com/ontodev/rdftab.rs/releases/download/v0.1.1/rdftab-x86_64-unknown-linux-musl 13 | SED = sed -i 14 | endif 15 | 16 | bin/rdftab: 17 | curl -L -o $@ $(RDFTAB_URL) 18 | chmod +x $@ 19 | 20 | RG_VERSION=2.3.0 21 | bin/relation-graph: 22 | curl -L -s https://github.com/balhoff/relation-graph/releases/download/v$(RG_VERSION)/relation-graph-cli-$(RG_VERSION).tgz | tar -zxv && mv relation-graph-cli-$(RG_VERSION) relation-graph && (cd bin && ln -s ../relation-graph/bin/relation-graph) 23 | 24 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: "Semantic SQL" 2 | theme: 3 | name: readthedocs 4 | analytics: 5 | gtag: G-2SYBSJVZ23 6 | plugins: 7 | - search 8 | - mermaid2 9 | nav: 10 | - Home: index.md 11 | - RDF: rdf_index.md 12 | - OWL: owl_index.md 13 | - OBO: obo_index.md 14 | - OMO: omo_index.md 15 | - Relation Graph: relation_graph_index.md 16 | site_url: https://incatools.github.io/semantic-sql/ 17 | repo_url: https://github.com/incatools/semantic-sql 18 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "semsql" 3 | version = "0.2.1" 4 | description = "" 5 | authors = ["cmungall "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.8.1" 10 | linkml-runtime = "^1.2.15" 11 | SQLAlchemy-Utils = "^0.38.2" 12 | click = "^8.1.3" 13 | 14 | [tool.poetry.dev-dependencies] 15 | mkdocs = "^1.3.0" 16 | linkml = ">=1.7.4" 17 | mkdocs-mermaid2-plugin = "^0.6.0" 18 | jupyter = "^1.0.0" 19 | ipython-sql = "^0.4.1" 20 | tox = "*" 21 | black = "*" 22 | 23 | [tool.poetry.scripts] 24 | semsql = "semsql.builder.cli:main" 25 | gen-semsql-views = "semsql.sqlutils.viewgen:cli" 26 | 27 | [build-system] 28 | requires = ["poetry-core>=1.0.0"] 29 | build-backend = "poetry.core.masonry.api" 30 | -------------------------------------------------------------------------------- /reports/obo.tsv: -------------------------------------------------------------------------------- 1 | ado 2 | agro 3 | aism 4 | amphx 5 | apo 6 | apollo_sv 7 | aro 8 | bco 9 | bfo 10 | bspo 11 | bto 12 | caro 13 | cdao 14 | cdno 15 | chebi 16 | cheminf 17 | chiro 18 | chmo 19 | cido 20 | cio 21 | cl 22 | clao 23 | clo 24 | clyh 25 | cmo 26 | cob 27 | colao 28 | cro 29 | cteno 30 | cto 31 | cvdo 32 | ddanat 33 | ddpheno 34 | dideo 35 | disdriv 36 | doid 37 | dpo 38 | dron 39 | duo 40 | ecao 41 | eco 42 | ecocore 43 | ecto 44 | emapa 45 | envo 46 | epio 47 | eupath 48 | exo 49 | fao 50 | fbbi 51 | fbbt 52 | fbcv 53 | fbdv 54 | fideo 55 | flopo 56 | fobi 57 | foodon 58 | fovt 59 | fypo 60 | gecko 61 | genepio 62 | geno 63 | geo 64 | gno 65 | go 66 | gsso 67 | hancestro 68 | hao 69 | hom 70 | hp 71 | hsapdv 72 | hso 73 | htn 74 | iao 75 | iceo 76 | ico 77 | ido 78 | ino 79 | kisao 80 | labo 81 | lepao 82 | ma 83 | maxo 84 | mco 85 | mf 86 | mfmo 87 | mfoem 88 | mfomd 89 | mi 90 | miapa 91 | micro 92 | mmo 93 | mmusdv 94 | mod 95 | mondo 96 | mop 97 | mp 98 | mpath 99 | mpio 100 | mro 101 | ms 102 | nbo 103 | ncbitaxon 104 | ncit 105 | ncro 106 | nomen 107 | oae 108 | oarcs 109 | oba 110 | obcs 111 | obi 112 | obib 113 | ogg 114 | ogms 115 | ogsf 116 | ohd 117 | ohmi 118 | ohpi 119 | olatdv 120 | omit 121 | omo 122 | omp 123 | omrse 124 | one 125 | ons 126 | ontoavida 127 | ontoneo 128 | oostt 129 | opl 130 | opmi 131 | ornaseq 132 | ovae 133 | pato 134 | pcl 135 | pco 136 | pdro 137 | pdumdv 138 | peco 139 | phipo 140 | plana 141 | planp 142 | po 143 | poro 144 | ppo 145 | pr 146 | proco 147 | psdo 148 | pso 149 | pw 150 | rbo 151 | ro 152 | rs 153 | rxno 154 | sbo 155 | scdo 156 | sepio 157 | so 158 | spd 159 | stato 160 | swo 161 | symp 162 | taxrank 163 | to 164 | trans 165 | tto 166 | txpo 167 | uberon 168 | uo 169 | upheno 170 | vbo 171 | vo 172 | vt 173 | vto 174 | wbbt 175 | wbls 176 | wbphenotype 177 | xao 178 | xco 179 | xlmod 180 | xpo 181 | zeco 182 | zfa 183 | zfs 184 | zp 185 | -------------------------------------------------------------------------------- /sparql/skos-to-owl.ru: -------------------------------------------------------------------------------- 1 | PREFIX skos: 2 | PREFIX owl: 3 | PREFIX rdf: 4 | PREFIX rdfs: 5 | PREFIX IAO: 6 | 7 | # Transform broader to subClassOf 8 | DELETE { 9 | ?concept skos:broader ?broaderConcept . 10 | } 11 | INSERT { 12 | ?concept rdf:type owl:Class . 13 | ?broaderConcept rdf:type owl:Class . 14 | ?concept rdfs:subClassOf ?broaderConcept . 15 | } 16 | WHERE { 17 | ?concept skos:broader ?broaderConcept . 18 | }; 19 | 20 | # Transform prefLabel to rdfs:label 21 | DELETE { 22 | ?concept skos:prefLabel ?label . 23 | } 24 | INSERT { 25 | ?concept rdfs:label ?label . 26 | } 27 | WHERE { 28 | ?concept skos:prefLabel ?label . 29 | }; 30 | 31 | # Transform SKOS definition to IAO definition 32 | DELETE { 33 | ?concept skos:definition ?definition . 34 | } 35 | INSERT { 36 | ?concept IAO:0000115 ?definition . 37 | } 38 | WHERE { 39 | ?concept skos:definition ?definition . 40 | }; 41 | 42 | 43 | DELETE { 44 | ?concept rdf:type skos:Concept . 45 | } 46 | INSERT { 47 | ?concept rdf:type owl:Class . 48 | } 49 | WHERE { 50 | ?concept rdf:type skos:Concept . 51 | }; 52 | 53 | 54 | DELETE { 55 | ?concept rdf:type owl:NamedIndividual . 56 | } 57 | WHERE { 58 | ?concept rdf:type owl:Class . 59 | }; 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/semsql/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/INCATools/semantic-sql/fca69e0604f60722a9a4927836dd55e4f88d2f91/src/semsql/__init__.py -------------------------------------------------------------------------------- /src/semsql/builder/build.Makefile: -------------------------------------------------------------------------------- 1 | # ---------------- 2 | # -- PARAMETERS -- 3 | # ---------------- 4 | 5 | # Directory containing this Makefile 6 | # https://stackoverflow.com/questions/18136918/how-to-get-current-relative-directory-of-your-makefile 7 | THIS_DIR := $(dir $(abspath $(firstword $(MAKEFILE_LIST)))) 8 | PREFIX_DIR = $(THIS_DIR)prefixes 9 | 10 | # Template sqlite database 11 | TEMPLATE = .template.db 12 | 13 | # intermediate RG files 14 | RGSUFFIX = relation-graph 15 | 16 | # path to relation-graph 17 | # (this is in the current path in odk docker) 18 | RG = relation-graph 19 | 20 | # ---------------- 21 | # -- TOP LEVEL -- 22 | # ---------------- 23 | all: help 24 | 25 | help: 26 | @echo "Type 'make /path/to/FOO.db' where FOO.owl exists in the same folder" 27 | 28 | # ---------------- 29 | # -- MAIN BUILD -- 30 | # ---------------- 31 | 32 | # -- TEMPLATE -- 33 | # All dbs are made from an initial template containing 34 | # (1) prefixes 35 | # (2) SQL Schema (primarily views) 36 | #$(TEMPLATE): $(THIS_DIR)/sql_schema/semsql.sql build_prefixes 37 | # cat $< | sqlite3 $@.tmp && \ 38 | # echo .exit | sqlite3 -echo $@.tmp -cmd ".mode csv" -cmd ".import $(THIS_DIR)/prefixes/prefixes.csv prefix" && \ 39 | # mv $@.tmp $@ 40 | #.PRECIOUS: $(TEMPLATE) 41 | 42 | %-min.owl: %.owl 43 | robot \ 44 | remove -i $< --axioms "equivalent disjoint annotation abox type" \ 45 | filter --exclude-terms $(THIS_DIR)/exclude-terms.txt \ 46 | -o $@ 47 | 48 | PREFIX_CSV_PATH = $(PREFIX_DIR)/prefixes.csv 49 | PREFIX_YAML_PATH = $(PREFIX_DIR)/prefixes.yaml 50 | 51 | # -- MAIN TARGET -- 52 | # A db is constructed from 53 | # (1) triples loaded using rdftab 54 | # (2) A relation-graph TSV 55 | %.db: %.owl %-$(RGSUFFIX).tsv $(PREFIX_CSV_PATH) 56 | rm -f $@.tmp && \ 57 | cat $(THIS_DIR)/sql_schema/semsql.sql | sqlite3 $@.tmp && \ 58 | echo .exit | sqlite3 -echo $@.tmp -cmd ".mode csv" -cmd ".import $(PREFIX_CSV_PATH) prefix" && \ 59 | rdftab $@.tmp < $< && \ 60 | sqlite3 $@.tmp -cmd '.separator "\t"' ".import $*-$(RGSUFFIX).tsv entailed_edge" && \ 61 | gzip -f $*-$(RGSUFFIX).tsv && \ 62 | cat $(THIS_DIR)/indexes/*.sql | sqlite3 $@.tmp && \ 63 | echo "ALTER TABLE statements ADD COLUMN graph TEXT;" | sqlite3 $@.tmp && \ 64 | (test -d views && find views -maxdepth 1 -name '$(notdir $*)*.sql' -type f -print0 | xargs -0 -I{} sh -c 'sqlite3 $@.tmp< "$$1"' sh {} || echo no views ) && \ 65 | mv $@.tmp $@ 66 | .PRECIOUS: %.db 67 | 68 | %-prefixes.yaml: $(PREFIX_CSV_PATH) 69 | grep -v ^prefix, $< | grep -v ^obo, | perl -npe 's@,(.*)@: "$$1"@' > $@.tmp && mv $@.tmp $@ 70 | 71 | # -- ENTAILED EDGES -- 72 | # relation-graph is used to compute entailed edges. 73 | %-$(RGSUFFIX).tsv: %-min.owl %-properties.txt %-prefixes.yaml 74 | $(RG) --disable-owl-nothing true \ 75 | --ontology-file $<\ 76 | $(RG_PROPERTIES) \ 77 | --output-file $@.tmp \ 78 | --equivalence-as-subclass true \ 79 | --mode TSV \ 80 | --prefixes $*-prefixes.yaml \ 81 | --output-individuals true \ 82 | --output-subclasses true \ 83 | --reflexive-subclasses true && \ 84 | mv $@.tmp $@ 85 | .PRECIOUS: %-$(RGSUFFIX).tsv 86 | 87 | %-properties.txt: 88 | touch $@ 89 | 90 | -------------------------------------------------------------------------------- /src/semsql/builder/builder.py: -------------------------------------------------------------------------------- 1 | import gzip 2 | import logging 3 | import os 4 | import shutil 5 | import subprocess 6 | from pathlib import Path 7 | from typing import List, Optional, TextIO 8 | 9 | import requests 10 | from linkml_runtime.loaders import yaml_loader 11 | from sqlalchemy import create_engine 12 | from sqlalchemy.orm import sessionmaker 13 | 14 | from semsql.builder.registry import path_to_ontology_registry, registry_schema 15 | from semsql.builder.registry.registry_schema import (CompressionEnum, Makefile, 16 | MakefileRule, Ontology) 17 | from semsql.utils.makefile_utils import makefile_to_string 18 | 19 | this_path = Path(__file__).parent 20 | 21 | 22 | class DockerConfig: 23 | """ 24 | Configuration for running ODK Docker image 25 | """ 26 | 27 | odk_version: str = None # not used yet 28 | memory: str = None 29 | 30 | 31 | def make( 32 | target: str, docker_config: Optional[DockerConfig] = None, prefix_csv_path=None 33 | ): 34 | """ 35 | Builds a target such as a SQLite file using the build.Makefile 36 | 37 | :param target: Make target 38 | :param docker_config: if passed, use ODK docker with the specific config 39 | :param prefix_csv_path: 40 | """ 41 | path_to_makefile = str(this_path / "build.Makefile") 42 | if docker_config is not None: 43 | mem = docker_config.memory 44 | if mem is None: 45 | mem = "4g" 46 | pwd = os.getcwd() 47 | pre = [ 48 | "docker", 49 | "run", 50 | "-m", 51 | mem, 52 | "-v", 53 | f"{pwd}/:/work", 54 | "-v", 55 | f"{this_path}/:/builder", 56 | "-w", 57 | "/work", 58 | "--rm", 59 | "-ti", 60 | "obolibrary/odkfull", 61 | ] 62 | path_to_makefile = "/builder/build.Makefile" 63 | else: 64 | pre = [] 65 | cmd = pre + ["make", target, "-f", path_to_makefile] 66 | if prefix_csv_path: 67 | cmd += [f"PREFIX_CSV_PATH={prefix_csv_path}"] 68 | logging.info(f"CMD={cmd}") 69 | subprocess.run(cmd) 70 | 71 | 72 | def db_from_owl(input: str) -> str: 73 | """ 74 | Creates a db file from a OWL file 75 | 76 | :param input: path to OWL file 77 | :return: path to db file 78 | """ 79 | if input.endswith(".owl"): 80 | db = input.replace(".owl", ".db") 81 | make(db) 82 | return db 83 | else: 84 | raise ValueError("Path must be an OWL file") 85 | 86 | 87 | def download_obo_sqlite(ontology: str, destination: str): 88 | """ 89 | Downloads pre-made SQLite file 90 | 91 | :param ontology: 92 | :param destination: 93 | :return: 94 | """ 95 | db = f"{ontology}.db" 96 | url = f"https://s3.amazonaws.com/bbop-sqlite/{db}.gz" 97 | logging.info(f"Downloading from {url}") 98 | r = requests.get(url, allow_redirects=True, timeout=3600) 99 | destination_gzip = f"{destination}.gz" 100 | open(destination_gzip, "wb").write(r.content) 101 | with gzip.open(destination_gzip, "rb") as f_in: 102 | with open(destination, "wb") as f_out: 103 | shutil.copyfileobj(f_in, f_out) 104 | os.remove(destination_gzip) 105 | 106 | 107 | def connect(owl_file: str): 108 | """ 109 | Generates a SQLite connection to an OWL file 110 | 111 | :param owl_file: 112 | :return: 113 | """ 114 | db = db_from_owl(owl_file) 115 | engine = create_engine(f"sqlite:///{db}") 116 | session = sessionmaker(bind=engine)() 117 | return session 118 | 119 | 120 | def get_postprocessing_steps( 121 | ontology: str, db: str, registry_path: str = None 122 | ) -> List[str]: 123 | """ 124 | Get postprocessing steps for an ontology 125 | 126 | :param registry_path: 127 | :param ontology: 128 | :return: 129 | """ 130 | if registry_path is None: 131 | registry_path = path_to_ontology_registry() 132 | registry: registry_schema.Registry 133 | registry = yaml_loader.load( 134 | str(registry_path), target_class=registry_schema.Registry 135 | ) 136 | # steps = [step.format(ont=ontology, db=db) for step in registry.ontologies.get(ontology, []).post_processing_steps] 137 | steps = registry.ontologies.get(ontology, []) 138 | return steps 139 | 140 | 141 | def compile_registry(registry_path: str, local_prefix_file: TextIO = None) -> str: 142 | """ 143 | Generate makefile content from registry 144 | 145 | :param registry_path: 146 | :param local_prefix_file: 147 | :return: 148 | """ 149 | registry: registry_schema.Registry 150 | registry = yaml_loader.load(registry_path, target_class=registry_schema.Registry) 151 | makefile = Makefile() 152 | onts = [] 153 | generic = Ontology("%", has_imports=True, suppress=True) 154 | if local_prefix_file: 155 | local_prefix_file.write("prefix,base\n") 156 | for ont in list(registry.ontologies.values()) + [generic]: 157 | # download target 158 | if ont == generic: 159 | command = "curl -L -s http://purl.obolibrary.org/obo/$*.owl > $@.tmp" 160 | elif ont.zip_extract_file: 161 | command = ( 162 | f"curl -L -s {ont.url} > $@.zip.tmp && " 163 | f"unzip -p $@.zip.tmp {ont.zip_extract_file} " 164 | "> $@.tmp && rm $@.zip.tmp" 165 | ) 166 | elif ont.compression: 167 | if str(ont.compression) == str(CompressionEnum.gzip.text): 168 | command = f"curl -L -s {ont.url} | gzip -dc > $@.tmp" 169 | else: 170 | raise ValueError(f"Unknown compression: '{ont.compression}'") 171 | else: 172 | command = f"curl -L -s {ont.url} > $@.tmp" 173 | download_rule = MakefileRule( 174 | target=f"download/{ont.id}.owl", 175 | dependencies=["STAMP"], 176 | commands=[ 177 | command, 178 | "sha256sum -b $@.tmp > $@.sha256", 179 | "mv $@.tmp $@", 180 | ], 181 | precious=True, 182 | ) 183 | makefile.rules.append(download_rule) 184 | # main build target 185 | target = f"db/{ont.id}.owl" 186 | dependencies = [f"download/{ont.id}.owl"] 187 | if ont.build_command: 188 | command = ont.build_command.format(ont=ont) 189 | elif ont.has_imports or (ont.format and ont.format != "rdfxml"): 190 | command = "robot merge -i $< -o $@" 191 | else: 192 | command = "cp $< $@" 193 | commands = [command] 194 | rule = MakefileRule(target=target, dependencies=dependencies, commands=commands) 195 | makefile.rules.append(rule) 196 | if not ont.suppress: 197 | onts.append(ont.id) 198 | if local_prefix_file: 199 | for pn in ont.prefixmap.values(): 200 | local_prefix_file.write(f"{pn.prefix},{pn.prefix_value}\n") 201 | mkfile = makefile_to_string(makefile) 202 | mkfile += f"EXTRA_ONTOLOGIES = {' '.join(onts)}" 203 | return mkfile 204 | -------------------------------------------------------------------------------- /src/semsql/builder/cli.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import subprocess 3 | from itertools import chain, combinations 4 | 5 | import click 6 | from linkml_runtime import SchemaView 7 | from linkml_runtime.utils.formatutils import underscore 8 | from sqlalchemy import text 9 | 10 | import semsql.builder.builder as builder 11 | from semsql.linkml import path_to_schema 12 | from semsql.sqlutils.viewgen import get_viewdef 13 | 14 | 15 | def powerset(iterable): 16 | """calculate powerset. 17 | 18 | See: https://docs.python.org/3/library/itertools.html#itertools-recipes 19 | 20 | >>> powerset([1,2,3]) 21 | () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3) 22 | """ 23 | s = list(iterable) 24 | return chain.from_iterable(combinations(s, r) for r in range(len(s) + 1)) 25 | 26 | 27 | @click.group() 28 | @click.option("-v", "--verbose", count=True) 29 | @click.option("-q", "--quiet") 30 | def main(verbose: int, quiet: bool): 31 | """Run the SemSQL CLI.""" 32 | if verbose >= 2: 33 | logging.basicConfig(level=logging.DEBUG) 34 | elif verbose == 1: 35 | logging.basicConfig(level=logging.INFO) 36 | else: 37 | logging.basicConfig(level=logging.WARNING) 38 | if quiet: 39 | logging.basicConfig(level=logging.ERROR) 40 | 41 | 42 | @main.command() 43 | @click.argument("path") 44 | @click.option( 45 | "--docker/--no-docker", 46 | default=False, 47 | show_default=True, 48 | help="Uses ODK docker image", 49 | ) 50 | @click.option("--prefix-csv-path", "-P", help="path to csv of prefix expansions") 51 | def make(path, docker, **kwargs): 52 | """ 53 | Makes a specified target, such as a db file 54 | 55 | Example: 56 | 57 | semsql make envo.db 58 | 59 | (assumes envo.owl is in the same folder) 60 | """ 61 | if docker: 62 | docker_config = builder.DockerConfig() 63 | else: 64 | docker_config = None 65 | builder.make(path, docker_config=docker_config, **kwargs) 66 | # check if path is db/{foo}.db using regular expression 67 | import re 68 | 69 | matches = re.match(r"db/(\w+).db", path) 70 | if matches: 71 | ontology = matches.group(1) 72 | steps = builder.get_postprocessing_steps(ontology, path) 73 | for step in steps: 74 | print(f"RUNNING: {step}") 75 | subprocess.run(step, shell=True) # noqa S602 76 | 77 | 78 | @main.command() 79 | @click.option( 80 | "--local-prefixes", 81 | "-P", 82 | type=click.File(mode="w"), 83 | help="path to local prefixes file (will be overridden)", 84 | ) 85 | @click.argument("registry") 86 | def generate_makefile(registry, local_prefixes): 87 | """ 88 | Generates makefile 89 | """ 90 | print(builder.compile_registry(registry, local_prefix_file=local_prefixes)) 91 | 92 | 93 | @main.command() 94 | @click.option("-o", "--output") 95 | @click.argument("ontology") 96 | def download(ontology, output): 97 | """ 98 | Download a read-made SQLite db for an OBO ontology 99 | 100 | Example: 101 | 102 | semsql download cl -o cl.db 103 | """ 104 | builder.download_obo_sqlite(ontology, destination=output) 105 | 106 | 107 | @main.command() 108 | @click.option("-i", "--input") 109 | @click.argument("query") 110 | def query(input, query): 111 | """ 112 | Performs a SQL query on an OWL file 113 | 114 | Example: 115 | 116 | semsql query -i hp.owl "SELECT * FROM rdfs_label_statement WHERE value LIKE 'Abnormality of %'" 117 | """ 118 | conn = builder.connect(input) 119 | statement = text(query) 120 | rs = conn.execute(statement) 121 | for row in rs: 122 | print(row) 123 | 124 | 125 | @main.command() 126 | @click.argument("views", nargs=-1) 127 | @click.option( 128 | "--index/--no-index", 129 | default=True, 130 | show_default=True, 131 | help="Create indexes on each column", 132 | ) 133 | @click.option( 134 | "--full-index/--no-full-index", 135 | default=False, 136 | show_default=True, 137 | help="Create indexes on all combos of columns (powerset)", 138 | ) 139 | @click.option( 140 | "--schema", 141 | "-s", 142 | help="Path o schema (optional)", 143 | ) 144 | def view2table(views, schema, index: bool, full_index: bool): 145 | """ 146 | Generates a command that turns a view into a table 147 | 148 | Example usage: 149 | 150 | semsql view2table rdfs_label_statement --index | sqlite3 db/pato.db 151 | 152 | """ 153 | if not schema: 154 | schema = str(path_to_schema()) 155 | sv = SchemaView(schema) 156 | for cn, c in sv.all_classes().items(): 157 | tn = underscore(cn) 158 | if not views or str(cn) in views or tn in views: 159 | view = get_viewdef(c) 160 | if view is not None: 161 | print(f"DROP VIEW {tn};") 162 | print(f"CREATE TABLE {tn} AS {view};") 163 | if index or full_index: 164 | colnames = [underscore(sn) for sn in sv.class_slots(cn)] 165 | for colname in colnames: 166 | print(f"CREATE INDEX {tn}_{colname} ON {tn}({colname});") 167 | if full_index: 168 | for combo in powerset(colnames): 169 | if len(combo) > 1: 170 | print( 171 | f"CREATE INDEX {tn}_{'_'.join(combo)} ON {tn}({','.join(combo)});" 172 | ) 173 | else: 174 | logging.info(f"No view for {cn}") 175 | 176 | 177 | if __name__ == "__main__": 178 | main() 179 | -------------------------------------------------------------------------------- /src/semsql/builder/exclude-terms.txt: -------------------------------------------------------------------------------- 1 | RO:0002222 2 | RO:0002244 3 | RO:0002320 4 | RO:0002321 5 | RO:0002323 6 | RO:0002324 7 | RO:0002330 8 | RO:0002337 9 | RO:0002375 10 | RO:0002410 11 | RO:0002487 12 | RO:0002501 13 | RO:0002506 14 | RO:0002514 15 | RO:0002567 16 | RO:0002595 17 | RO:0002609 18 | RO:0002616 19 | RO:0004000 20 | RO:0004010 21 | RO:0004023 22 | RO:0016001 23 | RO:0040035 24 | -------------------------------------------------------------------------------- /src/semsql/builder/indexes/all-indexes.sql: -------------------------------------------------------------------------------- 1 | CREATE INDEX statements_spo ON statements(subject,predicate,object); 2 | CREATE INDEX statements_spv ON statements(subject,predicate,value); 3 | CREATE INDEX statements_p ON statements(predicate); 4 | 5 | CREATE INDEX entailed_edge_spo on entailed_edge(subject, predicate, object); 6 | CREATE INDEX entailed_edge_sp on entailed_edge(subject, predicate); 7 | -------------------------------------------------------------------------------- /src/semsql/builder/prefixes/obo_prefixes.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/INCATools/semantic-sql/fca69e0604f60722a9a4927836dd55e4f88d2f91/src/semsql/builder/prefixes/obo_prefixes.db -------------------------------------------------------------------------------- /src/semsql/builder/prefixes/prefix.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS prefix ( 2 | prefix TEXT PRIMARY KEY, 3 | base TEXT NOT NULL 4 | ); 5 | 6 | INSERT OR IGNORE INTO prefix VALUES 7 | ("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"), 8 | ("rdfs", "http://www.w3.org/2000/01/rdf-schema#"), 9 | ("xsd", "http://www.w3.org/2001/XMLSchema#"), 10 | ("owl", "http://www.w3.org/2002/07/owl#"), 11 | ("oio", "http://www.geneontology.org/formats/oboInOwl#"), 12 | ("dce", "http://purl.org/dc/elements/1.1/"), 13 | ("dct", "http://purl.org/dc/terms/"), 14 | ("foaf", "http://xmlns.com/foaf/0.1/"), 15 | ("protege", "http://protege.stanford.edu/plugins/owl/protege#"), 16 | ("ex", "http://example.com/"), 17 | 18 | ("BFO", "http://purl.obolibrary.org/obo/BFO_"), 19 | ("CHEBI", "http://purl.obolibrary.org/obo/CHEBI_"), 20 | ("CL", "http://purl.obolibrary.org/obo/CL_"), 21 | ("RO", "http://purl.obolibrary.org/obo/RO_"), 22 | ("GO", "http://purl.obolibrary.org/obo/GO_"), 23 | ("ENVO", "http://purl.obolibrary.org/obo/ENVO_"), 24 | ("UBERON", "http://purl.obolibrary.org/obo/UBERON_"), 25 | ("PATO", "http://purl.obolibrary.org/obo/PATO_"), 26 | ("IAO", "http://purl.obolibrary.org/obo/IAO_"), 27 | ("NCBITaxon", "http://purl.obolibrary.org/obo/NCBITaxon_"), 28 | ("OBI", "http://purl.obolibrary.org/obo/OBI_"), 29 | ("PR", "http://purl.obolibrary.org/obo/PR_"), 30 | 31 | ("obo", "http://purl.obolibrary.org/obo/"), 32 | 33 | ("gocam", "http://model.geneontology.org/"), 34 | ("UP", "http://purl.uniprot.org/uniprot/"), 35 | ("UC", "http://purl.uniprot.org/core/"), 36 | ("PRO", "http://www.uniprot.org/annotation/PRO_"), 37 | ("faldo", "http://biohackathon.org/resource/faldo#"), 38 | 39 | ("ONTIE", "https://ontology.iedb.org/ontology/ONTIE_"), 40 | ("IEDB", "http://iedb.org/"), 41 | ("iedb-protein", "http://iedb.org/taxon-protein/"), 42 | ("CoVIC", "https://cvdb.ontodev.com/antibody/"), 43 | ("ds", "https://cvdb.ontodev.com/dataset/"), 44 | ("org", "https://cvdb.ontodev.com/organization/"), 45 | ("user", "https://cvdb.ontodev.com/user/"); 46 | -------------------------------------------------------------------------------- /src/semsql/builder/prefixes/prefix_ddl.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS prefix ( 2 | prefix TEXT PRIMARY KEY, 3 | base TEXT NOT NULL 4 | ); 5 | -------------------------------------------------------------------------------- /src/semsql/builder/prefixes/prefixes_curated.csv: -------------------------------------------------------------------------------- 1 | prefix,base 2 | rdf,http://www.w3.org/1999/02/22-rdf-syntax-ns# 3 | rdfs,http://www.w3.org/2000/01/rdf-schema# 4 | xsd,http://www.w3.org/2001/XMLSchema# 5 | owl,http://www.w3.org/2002/07/owl# 6 | oio,http://www.geneontology.org/formats/oboInOwl# 7 | dce,http://purl.org/dc/elements/1.1/ 8 | dcterms,http://purl.org/dc/terms/ 9 | foaf,http://xmlns.com/foaf/0.1/ 10 | protege,http://protege.stanford.edu/plugins/owl/protege# 11 | skos,http://www.w3.org/2004/02/skos/core# 12 | dbpedia,http://dbpedia.org/resource/ 13 | ex,http://example.org/ 14 | ex2,https://example.org/ 15 | obo,http://purl.obolibrary.org/obo/ 16 | gocam,http://model.geneontology.org/ 17 | EDAM,http://edamontology.org/ 18 | EFO,http://www.ebi.ac.uk/efo/EFO_ 19 | ORDO,http://www.orpha.net/ORDO/Orphanet_ 20 | UP,http://purl.uniprot.org/uniprot/ 21 | UC,http://purl.uniprot.org/core/ 22 | PRO,http://www.uniprot.org/annotation/PRO_ 23 | faldo,http://biohackathon.org/resource/faldo# 24 | ONTIE,https://ontology.iedb.org/ontology/ONTIE_ 25 | IEDB,http://iedb.org/ 26 | iedb-protein,http://iedb.org/taxon-protein/ 27 | CoVIC,https://cvdb.ontodev.com/antibody/ 28 | EXAMPLE,http://purl.obolibrary.org/obo/EXAMPLE_ 29 | ENM,http://purl.enanomapper.org/onto/ENM_ 30 | BAO,http://www.bioassayontology.org/bao#BAO_ 31 | biopax,http://www.biopax.org/release/biopax-level3.owl# 32 | reactome.biopax,http://www.reactome.org/biopax/81/48887# 33 | sweet,http://sweetontology.net/ 34 | qudt,http://qudt.org/schema/qudt# 35 | allotrope.property,http://purl.allotrope.org/ontologies/property# 36 | allotrope.role,http://purl.allotrope.org/ontologies/role# 37 | allotrope.equipment,http://purl.allotrope.org/ontologies/equipment# 38 | allotrope,http://purl.allotrope.org/ontologies/ 39 | aio,https://w3id.org/aio/ 40 | NPO,http://purl.bioontology.org/ontology/npo#NPO_ 41 | CHEMINF,http://semanticscience.org/resource/CHEMINF_ 42 | aopkb,http://aopkb.org/aop_ontology# 43 | ENM.net,http://purl.enanomapper.net/onto/ENM_ 44 | cito,http://purl.org/spar/cito/ 45 | fabio,http://purl.org/spar/fabio/ 46 | SIO,http://semanticscience.org/resource/SIO_ 47 | doap,http://usefulinc.com/ns/doap# 48 | wikidata,http://www.wikidata.org/entity/ 49 | schema,https://schema.org/ 50 | ICD10CM,http://purl.bioontology.org/ontology/ICD10CM/ 51 | snomedct,http://snomed.info/id/ 52 | cc,http://creativecommons.org/licenses/ 53 | HGNC,http://identifiers.org/hgnc/ 54 | MEDDRA,http://identifiers.org/meddra/ 55 | MESH,http://identifiers.org/mesh/ 56 | MEDGEN,http://identifiers.org/medgen/ 57 | SCTID,http://identifiers.org/snomedct/ 58 | UMLS,http://linkedlifedata.com/resource/umls/id/ 59 | orcid,https://orcid.org/ 60 | swrl,http://www.w3.org/2003/11/swrl# 61 | semapv,https://w3id.org/semapv/vocab/ 62 | sssom,https://w3id.org/sssom/ 63 | CLINGEN,https://www.clinicalgenome.org/affiliation/ 64 | -------------------------------------------------------------------------------- /src/semsql/builder/prefixes/prefixes_local.csv: -------------------------------------------------------------------------------- 1 | prefix,base 2 | SWO,http://www.ebi.ac.uk/swo/SWO_ 3 | SWO.organization,http://www.ebi.ac.uk/swo/organization/SWO_ 4 | SWO.maturity,http://www.ebi.ac.uk/swo/maturity/SWO_ 5 | SWO.objective,http://www.ebi.ac.uk/swo/objective/SWO_ 6 | SWO.license,http://www.ebi.ac.uk/swo/license/SWO_ 7 | SWO.version,http://www.ebi.ac.uk/swo/version/SWO_ 8 | SWO.data,http://www.ebi.ac.uk/swo/data/SWO_ 9 | SWO.interface,http://www.ebi.ac.uk/swo/interface/SWO_ 10 | SWO.algorithm,http://www.ebi.ac.uk/swo/algorithm/SWO_ 11 | OntoDM,http://www.ontodm.com/OntoDM-core/OntoDM_ 12 | OntoDM.kdd,http://kt.ijs.si/panovp/OntoDM#OntoDM-KDD 13 | PCL,http://purl.obolibrary.org/obo/PCL_ 14 | ENSEMBL,http://identifiers.org/ensembl/ 15 | chemrof,https://w3id.org/chemrof/ 16 | OGCO,http://purl.obolibrary.org/obo/OGCO_ 17 | FMA,http://purl.org/sig/ont/fma/fma 18 | MSIO,http://purl.obolibrary.org/obo/MSIO_ 19 | nmrCV,http://nmrML.org/nmrCV#NMR: 20 | PRIDE,http://purl.obolibrary.org/obo/PRIDE_ 21 | modl,https://archive.org/services/purl/purl/modular_ontology_design_library# 22 | biolink,https://w3id.org/biolink/vocab/ 23 | biolink,https://w3id.org/biolink/vocab/ 24 | loinc,https://loinc.org/ 25 | HBA,https://purl.brain-bican.org/ontology/hbao/HBA_ 26 | MBA,https://purl.brain-bican.org/ontology/mbao/MBA_ 27 | DMBA,https://purl.brain-bican.org/ontology/dmbao/DMBA_ 28 | DHBA,https://purl.brain-bican.org/ontology/dhbao/DHBA_ 29 | PBA,https://purl.brain-bican.org/ontology/pbao/PBA_ 30 | XSMO,http://purl.obolibrary.org/obo/XSMO_ 31 | BCIO,http://humanbehaviourchange.org/ontology/BCIO_ 32 | BCIOR,http://humanbehaviourchange.org/ontology/BCIOR_ 33 | SIO,http://semanticscience.org/resource/SIO_ 34 | icd10who,https://icd.who.int/browse10/2019/en#/ 35 | GARD,http://purl.obolibrary.org/obo/GARD_ 36 | OMIM,https://omim.org/entry/ 37 | OMIMPS,https://www.omim.org/phenotypicSeries/PS 38 | CHR,http://purl.obolibrary.org/obo/CHR_ 39 | OEO,http://openenergy-platform.org/ontology/oeo/OEO_ 40 | OEOX,http://openenergy-platform.org/ontology/oeo/OEOX_ 41 | envthes,http://vocabs.lter-europe.net/EnvThes/ 42 | omv,http://omv.ontoware.org/2005/05/ 43 | iadopt,https://w3id.org/iadopt/ont/ 44 | lternet.tema,https://vocab.lternet.edu/vocab/vocab/?tema= 45 | webprotege,http://webprotege.stanford.edu/ 46 | GOLDTERMS,https://w3id.org/gold.path/ 47 | GOLDVOCAB,https://w3id.org/gold.vocab/ 48 | SDGIO,http://purl.unep.org/sdg/SDGIO_ 49 | KIN,http://purl.org/ga4gh/kin.owl#KIN_ 50 | ontorion,http://ontorion.com/namespace# 51 | omop,https://athena.ohdsi.org/search-terms/terms/ 52 | comet,https://w3id.org/linkml-common/ 53 | CCO,http://www.ontologyrepository.com/CommonCoreOntologies/ 54 | OccO,http://purl.obolibrary.org/obo/OccO_ 55 | IOFcore,https://spec.industrialontologies.org/ontology/ 56 | UPa,http://purl.obolibrary.org/obo/UPa_ 57 | orcid,https://orcid.org/ 58 | ror,https://ror.org/ 59 | evs.ncit,http://ncicb.nci.nih.gov/xml/owl/EVS/Thesaurus.owl# 60 | old.fix,http://purl.org/obo/owl/FIX# 61 | mlo,http://www.a2rd.net.br/mlo# 62 | ito,https://identifiers.org/ito: 63 | CHEMONTID,http://purl.obolibrary.org/obo/CHEMONTID_ 64 | cso,https://cso.kmi.open.ac.uk/topics/ 65 | freebase,http://rdf.freebase.com/ns/ 66 | yago,http://yago-knowledge.org/resource/ 67 | cyc,http://sw.cyc.com/concept 68 | OBIws,http://purl.obolibrary.org/obo/OBIws_ 69 | reactome.obo,http://purl.obolibrary.org/obo/reactome_ 70 | reactome.biopax,http://www.reactome.org/biopax/77/48887# 71 | chr,http://purl.obolibrary.org/obo/CHR_ 72 | oboe-core,http://ecoinformatics.org/oboe/oboe.1.0/oboe-core.owl# 73 | oboe-standards,http://ecoinformatics.org/oboe/oboe.1.0/oboe-standards.owl# 74 | prov,http://www.w3.org/ns/prov# 75 | dtype,http://www.linkedmodel.org/schema/dtype# 76 | voag,http://voag.linkedmodel.org/voag# 77 | vaem,http://www.linkedmodel.org/schema/vaem# 78 | qudtschema,http://qudt.org/schema/qudt/ 79 | qudtunit,http://qudt.org/vocab/unit/ 80 | si-quantity,https://si-digital-framework.org/SI/quantities/ 81 | quantitykind,http://qudt.org/vocab/quantitykind/ 82 | cellosaurus,http://purl.obolibrary.org/obo/Cellosaurus#CVCL_ 83 | fhkb,http://owl.cs.manchester.ac.uk/tutorials/fhkb# 84 | dbpediaont,http://dbpedia.org/ontology/ 85 | dbpedia,http://dbpedia.org/ 86 | ICD10CM,http://purl.bioontology.org/ontology/ICD10CM/ 87 | OMIM,https://omim.org/entry/ 88 | OMIMPS,https://www.omim.org/phenotypicSeries/PS 89 | co_324,https://cropontology.org/rdf/CO_324: 90 | PPEO,http://purl.org/ppeo/PPEO.owl# 91 | InterPro,http://purl.obolibrary.org/obo/InterPro_ 92 | PFAM,https://www.ebi.ac.uk/interpro/entry/pfam/ 93 | PFAM.CLAN,https://www.ebi.ac.uk/interpro/set/pfam/ 94 | hgnc.genegroup,http://purl.obolibrary.org/obo/hgnc.genegroup_ 95 | hgnc,http://purl.obolibrary.org/obo/hgnc_ 96 | SGD,https://www.yeastgenome.org/locus/ 97 | gtdb,https://gtdb.ecogenomic.org/tree?r= 98 | EC,https://bioregistry.io/eccode: 99 | UniProtKB,https://bioregistry.io/uniprot: 100 | UniProtKB,https://bioregistry.io/uniprot: 101 | RESID,https://proteininformationresource.org/cgi-bin/resid?id= 102 | UNIMOD,http://www.unimod.org/modifications_view.php?editid1= 103 | uniprot.ptm,https://biopragmatics.github.io/providers/uniprot.ptm/ 104 | credit,https://credit.niso.org/contributor-roles/ 105 | RHEA,https://www.rhea-db.org/rhea/ 106 | swisslipid,http://purl.obolibrary.org/obo/swisslipid_ 107 | drugbank,http://purl.obolibrary.org/obo/drugbank_ 108 | drugbank,http://purl.obolibrary.org/obo/drugcentral_ 109 | complexportal,http://purl.obolibrary.org/obo/complexportal_ 110 | complexportal,http://purl.obolibrary.org/obo/wikipathways_ 111 | pathbank,http://purl.obolibrary.org/obo/pathbank_ 112 | pathbank,http://purl.obolibrary.org/obo/kegg.genome_ 113 | MESH,http://id.nlm.nih.gov/mesh/ 114 | RXNORM,http://purl.bioontology.org/ontology/RXNORM/ 115 | VCCF,http://purl.obolibrary.org/obo/VCCF_ 116 | OBT,http://purl.obolibrary.org/obo/OBT_ 117 | NANDO,http://nanbyodata.jp/ontology/NANDO_ 118 | ECSO,http://purl.dataone.org/odo/ECSO_ 119 | ONTIE,https://ontology.iedb.org/ontology/ONTIE_ 120 | ECOSIM,http://purl.obolibrary.org/obo/ECOSIM_ 121 | ECOSIMCONCEPT,http://purl.obolibrary.org/obo/ECOSIMCONCEPT_ 122 | nmdc,https://w3id.org/nmdc/ 123 | linkml,https://w3id.org/linkml/ 124 | mixs,https://w3id.org/mixs/ 125 | mixs,https://w3id.org/mixs/ 126 | kgcl,https://w3id.org/kgcl/ 127 | fibo,https://spec.edmcouncil.org/fibo/ontology/ 128 | cmnsav,https://www.omg.org/spec/Commons/AnnotationVocabulary/ 129 | -------------------------------------------------------------------------------- /src/semsql/builder/registry/__init__.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | this_path = Path(__file__).parent 4 | 5 | 6 | def path_to_ontology_registry() -> Path: 7 | """ 8 | Returns path to the registry file 9 | 10 | :return: 11 | """ 12 | return this_path / "ontologies.yaml" 13 | -------------------------------------------------------------------------------- /src/semsql/builder/registry/registry_schema.yaml: -------------------------------------------------------------------------------- 1 | id: https://w3id.org/semsql/registry 2 | name: ontology_registry 3 | description: Ontology Registry 4 | imports: 5 | - linkml:types 6 | prefixes: 7 | linkml: https://w3id.org/linkml/ 8 | semsql_registry: https://w3id.org/semsql/registry 9 | default_prefix: semsql_registry 10 | types: 11 | identifier: 12 | typeof: string 13 | https identifier: 14 | typeof: string 15 | http identifier: 16 | typeof: string 17 | enums: 18 | format_enum: 19 | permissible_values: 20 | n3: 21 | description: n3 22 | compression_enum: 23 | permissible_values: 24 | gzip: 25 | description: gzip 26 | slots: 27 | id: 28 | examples: 29 | - value: lov 30 | identifier: true 31 | range: string 32 | description: 33 | examples: 34 | - value: Monarch Phenomics Integrated Ontology 35 | range: string 36 | url: 37 | examples: 38 | - value: https://lov.linkeddata.es/lov.n3.gz 39 | range: identifier 40 | relation_graph_settings: 41 | range: RelationGraphConfiguration 42 | has_imports: 43 | examples: 44 | - value: 'True' 45 | range: integer 46 | jsonld_context: 47 | examples: 48 | - value: https://biolink.github.io/biolink-model/context.jsonld 49 | range: https identifier 50 | prefixmap: 51 | examples: 52 | - value: '[''$ref:Prefixmap'', ''$ref:Prefixmap'']' 53 | multivalued: true 54 | range: PrefixMap 55 | inlined: true 56 | zip_extract_file: 57 | description: file to extract in a zip download 58 | named_prefixmaps: 59 | examples: 60 | - value: '[''sweet'', ''prefixcc'']' 61 | multivalued: true 62 | range: string 63 | format: 64 | examples: 65 | - value: n3 66 | #range: format_enum 67 | build_command: 68 | sha256: 69 | local_path: 70 | compression: 71 | examples: 72 | - value: gzip 73 | range: compression_enum 74 | prefix: 75 | examples: 76 | - value: fix 77 | identifier: true 78 | range: string 79 | prefix_value: 80 | examples: 81 | - value: http://purl.org/obo/owl/FIX# 82 | range: http identifier 83 | license: 84 | examples: 85 | - value: CC0 86 | range: string 87 | ontologies: 88 | examples: 89 | - value: '[''$ref:Ontology'', ''$ref:Ontology'', ''$ref:Ontology'', ''$ref:Ontology'', 90 | ''$ref:Ontology'', ''$ref:Ontology'', ''$ref:Ontology'', ''$ref:Ontology'', 91 | ''$ref:Ontology'', ''$ref:Ontology'', ''$ref:Ontology'', ''$ref:Ontology'', 92 | ''$ref:Ontology'']' 93 | multivalued: true 94 | inlined: true 95 | inlined_as_list: false 96 | range: Ontology 97 | suppress: 98 | range: boolean 99 | post_processing_steps: 100 | multivalued: true 101 | range: string 102 | classes: 103 | Ontology: 104 | slots: 105 | - description 106 | - url 107 | - id 108 | - sha256 109 | - local_path 110 | - has_imports 111 | - jsonld_context 112 | - prefixmap 113 | - named_prefixmaps 114 | - format 115 | - compression 116 | - suppress 117 | - relation_graph_settings 118 | - zip_extract_file 119 | - build_command 120 | - post_processing_steps 121 | PrefixMap: 122 | slots: 123 | - prefix 124 | - prefix_value 125 | Registry: 126 | slots: 127 | - id 128 | - description 129 | - license 130 | - ontologies 131 | tree_root: true 132 | RelationGraphConfiguration: 133 | attributes: 134 | properties: 135 | range: uriorcurie 136 | multivalued: true 137 | MakefileRule: 138 | attributes: 139 | target: 140 | dependencies: 141 | multivalued: true 142 | commands: 143 | multivalued: true 144 | comments: 145 | multivalued: true 146 | precious: 147 | range: boolean 148 | Makefile: 149 | attributes: 150 | rules: 151 | range: MakefileRule 152 | inlined: true 153 | multivalued: true 154 | -------------------------------------------------------------------------------- /src/semsql/linkml/__init__.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | this_path = Path(__file__).parent 4 | 5 | 6 | def path_to_schema(schema_name: str = "semsql") -> Path: 7 | """ 8 | Returns path to the name of a schema 9 | 10 | :param schema_name: defaults to global schema (semsql) 11 | :return: 12 | """ 13 | return this_path / f"{schema_name}.yaml" 14 | -------------------------------------------------------------------------------- /src/semsql/linkml/basics.yaml: -------------------------------------------------------------------------------- 1 | name: basics 2 | id: https://w3id.org/semsql/basics 3 | imports: 4 | - linkml:types 5 | license: https://creativecommons.org/publicdomain/zero/1.0/ 6 | prefixes: 7 | semsql: https://w3id.org/semsql/ 8 | linkml: https://w3id.org/linkml/ 9 | default_curi_maps: 10 | - semweb_context 11 | default_prefix: semsql 12 | default_range: string 13 | slots: 14 | id: 15 | identifier: true 16 | source_file: ../semantic-sql/src/linkml/basics.yaml 17 | -------------------------------------------------------------------------------- /src/semsql/linkml/chebi.yaml: -------------------------------------------------------------------------------- 1 | id: https://w3id.org/semsql/ro 2 | name: ro 3 | title: ro module 4 | description: |- 5 | Module for RO relations 6 | 7 | license: https://creativecommons.org/publicdomain/zero/1.0/ 8 | 9 | prefixes: 10 | semsql: https://w3id.org/semsql/ 11 | linkml: https://w3id.org/linkml/ 12 | chebivocab: "http://purl.obolibrary.org/obo/chebi#" 13 | 14 | default_prefix: semsql 15 | default_range: string 16 | 17 | default_curi_maps: 18 | - semweb_context 19 | 20 | imports: 21 | - rdf 22 | - owl 23 | - relation_graph 24 | 25 | classes: 26 | 27 | conjugate_acid_of_edge: 28 | slot_uri: chebivocab:is_conjugate_acid_of 29 | is_a: edge 30 | classification_rules: 31 | is_a: edge 32 | slot_conditions: 33 | predicate: 34 | equals_string: "obo:chebi#is_conjugate_acid_of" 35 | conjugate_base_of_edge: 36 | slot_uri: chebivocab:is_conjugate_base_of 37 | is_a: edge 38 | classification_rules: 39 | is_a: edge 40 | slot_conditions: 41 | predicate: 42 | equals_string: "obo:chebi#is_conjugate_base_of" 43 | charge_statement: 44 | slot_uri: chebivocab:charge 45 | is_a: edge 46 | comments: 47 | - sqlview>> SELECT subject, predicate, cast(value AS "int") AS value FROM statements WHERE predicate = 'obo:chebi/charge'; 48 | -------------------------------------------------------------------------------- /src/semsql/linkml/nlp.yaml: -------------------------------------------------------------------------------- 1 | name: semsql_nlp 2 | description: Module for representing results of simple NLTK/SpaCy type processing 3 | title: Semantic SQL NLP/TM module 4 | id: https://w3id.org/semsql/nlp 5 | imports: 6 | - rdf 7 | license: https://creativecommons.org/publicdomain/zero/1.0/ 8 | prefixes: 9 | semsql: https://w3id.org/semsql/ 10 | linkml: https://w3id.org/linkml/ 11 | default_curi_maps: 12 | - semweb_context 13 | default_prefix: semsql 14 | default_range: string 15 | slots: 16 | transformation_predicate: {} 17 | transformed_value: {} 18 | classes: 19 | subject_prefix: 20 | description: This may move to another module as it is generally useful 21 | comments: 22 | - sqlview>> SELECT DISTINCT s.subject, prefix.prefix AS value FROM prefix, statements 23 | AS s WHERE INSTR(s.subject,prefix || ':')=1; 24 | slots: 25 | - subject 26 | - value 27 | textual_transformation: 28 | description: Represents a transformation of a subject text value, e.g. lemmatization 29 | slots: 30 | - subject 31 | - predicate 32 | - value 33 | slot_usage: 34 | subject: 35 | description: The string value prior to processing. This may be for example 36 | the value of an rdfs_label_statement 37 | range: string 38 | predicate: 39 | description: The relation between subject and object, e.g stemming 40 | object: 41 | description: The string value after processing. E.g. lemmatized value 42 | range: string 43 | processed_statement: 44 | description: A statement that is enhanced with a processed/transformed text value 45 | comments: 46 | - "sqlview>>\n SELECT s.*, t.predicate AS transformation_predicate, t.value AS\ 47 | \ transformed_value\n FROM statements AS s JOIN textual_transformation AS t\ 48 | \ ON(s.value=t.subject)\n WHERE datatype != 'xsd:boolean'" 49 | slots: 50 | - subject 51 | - predicate 52 | - value 53 | - transformation_predicate 54 | - transformed_value 55 | match: 56 | description: 'TODO: Reuse SSSOM here' 57 | comments: 58 | - "sqlview>>\n SELECT\n s1.subject AS subject_id,\n s1l.value AS subject_label,\n\ 59 | \ s1.predicate AS subject_match_field,\n s1p.value AS subject_source,\n\ 60 | \ s1.transformation_predicate AS subject_preprocessing,\n s2.subject AS\ 61 | \ object_id,\n s2l.value AS object_label,\n s2.predicate AS object_match_field,\n\ 62 | \ s2p.value AS object_source,\n s2.transformation_predicate AS object_preprocessing,\n\ 63 | \ s1.transformed_value AS match_field\n FROM\n processed_statement AS\ 64 | \ s1\n JOIN processed_statement AS s2 ON (s1.transformed_value = s2.transformed_value)\n\ 65 | \ JOIN rdfs_label_statement AS s1l ON (s1.subject=s1l.subject)\n JOIN\ 66 | \ rdfs_label_statement AS s2l ON (s2.subject=s2l.subject)\n JOIN subject_prefix\ 67 | \ AS s1p ON (s1.subject=s1p.subject)\n JOIN subject_prefix AS s2p ON (s2.subject=s2p.subject)\n\ 68 | \ WHERE s1.subject != s2.subject" 69 | attributes: 70 | subject_id: {} 71 | subject_label: {} 72 | subject_source: {} 73 | subject_match_field: {} 74 | subject_preprocessing: {} 75 | object_id: {} 76 | object_label: {} 77 | object_source: {} 78 | object_match_field: {} 79 | object_preprocessing: {} 80 | match_string: {} 81 | source_file: ../semantic-sql/src/linkml/nlp.yaml 82 | -------------------------------------------------------------------------------- /src/semsql/linkml/obo.yaml: -------------------------------------------------------------------------------- 1 | name: semsql_obo 2 | description: Abstractions for working with OBO ontologies. Assumes usage of OMO. Currently 3 | this module contains mostly checks/reports, similar to ROBOT report 4 | title: Semantic SQL OBO module 5 | id: https://w3id.org/semsql/obo 6 | imports: 7 | - rdf 8 | - owl 9 | - omo 10 | license: https://creativecommons.org/publicdomain/zero/1.0/ 11 | prefixes: 12 | semsql: https://w3id.org/semsql/ 13 | linkml: https://w3id.org/linkml/ 14 | default_curi_maps: 15 | - semweb_context 16 | default_prefix: semsql 17 | default_range: string 18 | classes: 19 | ontology_status_statement: 20 | comments: 21 | - sqlview>> SELECT * FROM statements WHERE predicate = '' 22 | - sqlview>> SELECT * FROM statements WHERE predicate = 'pav:status' 23 | is_a: node_to_value_statement 24 | repair_action: 25 | description: Represents an action that needs to be taken to repair a problem 26 | abstract: true 27 | slots: 28 | - subject 29 | - description 30 | slot_usage: 31 | subject: 32 | description: The thing that is problematic 33 | problem: 34 | description: Represents an instance of a problem pertaining to conformance to 35 | OBO guidelines 36 | abstract: true 37 | slots: 38 | - subject 39 | - predicate 40 | - value 41 | slot_usage: 42 | subject: 43 | description: The thing that is problematic 44 | predicate: 45 | description: The property of the thing that is problematic 46 | lexical_problem: 47 | description: a problem with the textual value of an annotation property 48 | is_a: problem 49 | abstract: true 50 | trailing_whitespace_problem: 51 | comments: 52 | - "sqlview>> SELECT\n subject,\n predicate,\n value\nFROM statements WHERE\ 53 | \ VALUE like ' %' OR VALUE like '% '" 54 | is_a: problem 55 | property_used_with_datatype_values_and_objects: 56 | description: A problem in which the same property is used two two different ways, 57 | one in which the range is a literal value, the other where it is an object. 58 | comments: 59 | - in OWL, if a property is typed as object or datatype, this will constrain its 60 | use. However, annotation properties have no such constraints in OWL, but we 61 | still consider this problematic and unintentional, unless it is a truly generic 62 | predicate, such as owl:annotatedTarget or rdf:object 63 | - "sqlview>>\n SELECT\n DISTINCT\n s1.predicate AS subject,\n s1.predicate,\n\ 64 | \ s1.datatype AS value\n FROM statements AS s1, statements AS s2 ON (s1.predicate=s2.predicate)\n\ 65 | \ WHERE s1.value IS NOT NULL and s2.object IS NOT NULL" 66 | see_also: 67 | - https://github.com/information-artifact-ontology/ontology-metadata/issues/67 68 | is_a: problem 69 | node_with_two_labels_problem: 70 | comments: 71 | - "sqlview>>\n SELECT\n s1.subject,\n s1.predicate,\n s1.value\n\ 72 | \ FROM rdfs_label_statement AS s1, rdfs_label_statement AS s2\n WHERE s1.subject=s2.subject\ 73 | \ AND s1.value != s2.value" 74 | is_a: problem 75 | attributes: 76 | label1: {} 77 | label2: {} 78 | all_problems: 79 | comments: 80 | - sqlview>> SELECT * FROM node_with_two_labels_problem 81 | - sqlview>> SELECT * FROM trailing_whitespace_problem 82 | is_a: problem 83 | source_file: ../semantic-sql/src/linkml/obo.yaml 84 | -------------------------------------------------------------------------------- /src/semsql/linkml/omo.yaml: -------------------------------------------------------------------------------- 1 | name: semsql_omo 2 | description: Module representing OBO Metadata Ontology abstractions 3 | title: Semantic SQL OMO module 4 | see_also: 5 | - http://obofoundry.org/ontology/omo 6 | id: https://w3id.org/semsql/omo 7 | imports: 8 | - rdf 9 | - owl 10 | license: https://creativecommons.org/publicdomain/zero/1.0/ 11 | prefixes: 12 | semsql: https://w3id.org/semsql/ 13 | linkml: https://w3id.org/linkml/ 14 | default_curi_maps: 15 | - semweb_context 16 | default_prefix: semsql 17 | default_range: string 18 | classes: 19 | has_text_definition_statement: 20 | is_a: node_to_value_statement 21 | classification_rules: 22 | is_a: statements 23 | slot_conditions: 24 | predicate: 25 | equals_string: "IAO:0000115" 26 | has_oio_synonym_statement: 27 | is_a: node_to_value_statement 28 | abstract: true 29 | union_of: 30 | - has_exact_synonym_statement 31 | - has_broad_synonym_statement 32 | - has_narrow_synonym_statement 33 | - has_related_synonym_statement 34 | has_exact_synonym_statement: 35 | exact_mappings: 36 | - oio:hasExactSynonym 37 | is_a: has_oio_synonym_statement 38 | classification_rules: 39 | is_a: statements 40 | slot_conditions: 41 | predicate: 42 | equals_string: "oio:hasExactSynonym" 43 | has_broad_synonym_statement: 44 | exact_mappings: 45 | - oio:hasBroadSynonym 46 | is_a: has_oio_synonym_statement 47 | classification_rules: 48 | is_a: statements 49 | slot_conditions: 50 | predicate: 51 | equals_string: "oio:hasBroadSynonym" 52 | has_narrow_synonym_statement: 53 | exact_mappings: 54 | - oio:hasNarrowSynonym 55 | comments: 56 | - sqlview>> SELECT * FROM statements WHERE predicate='oio:hasNarrowSynonym' 57 | is_a: has_oio_synonym_statement 58 | classification_rules: 59 | is_a: statements 60 | slot_conditions: 61 | predicate: 62 | equals_string: "oio:hasNarrowSynonym" 63 | has_related_synonym_statement: 64 | exact_mappings: 65 | - oio:hasRelatedSynonym 66 | comments: 67 | - sqlview>> SELECT * FROM statements WHERE predicate='oio:hasRelatedSynonym' 68 | is_a: has_oio_synonym_statement 69 | classification_rules: 70 | is_a: statements 71 | slot_conditions: 72 | predicate: 73 | equals_string: "oio:hasRelatedSynonym" 74 | has_synonym_statement: 75 | is_a: node_to_value_statement 76 | union_of: 77 | - has_exact_synonym_statement 78 | - has_broad_synonym_statement 79 | - has_narrow_synonym_statement 80 | - has_related_synonym_statement 81 | has_exact_match_statement: 82 | exact_mappings: 83 | - skos:hasExactMatch 84 | comments: 85 | - sqlview>> SELECT * FROM statements WHERE predicate='skos:hasExactMatch' 86 | is_a: has_match_statement 87 | classification_rules: 88 | is_a: statements 89 | slot_conditions: 90 | predicate: 91 | equals_string: "skos:hasExactMatch" 92 | has_broad_match_statement: 93 | exact_mappings: 94 | - skos:hasBroadMatch 95 | comments: 96 | - sqlview>> SELECT * FROM statements WHERE predicate='skos:hasBroadMatch' 97 | is_a: has_match_statement 98 | classification_rules: 99 | is_a: statements 100 | slot_conditions: 101 | predicate: 102 | equals_string: "skos:hasBroadMatch" 103 | has_narrow_match_statement: 104 | exact_mappings: 105 | - skos:hasNarrowMatch 106 | comments: 107 | - sqlview>> SELECT * FROM statements WHERE predicate='skos:hasNarrowMatch' 108 | is_a: has_match_statement 109 | classification_rules: 110 | is_a: statements 111 | slot_conditions: 112 | predicate: 113 | equals_string: "skos:hasNarrowMatch" 114 | has_related_match_statement: 115 | exact_mappings: 116 | - skos:hasRelatedMatch 117 | comments: 118 | - sqlview>> SELECT * FROM statements WHERE predicate='skos:hasRelatedMatch' 119 | is_a: has_match_statement 120 | classification_rules: 121 | is_a: statements 122 | slot_conditions: 123 | predicate: 124 | equals_string: "skos:hasRelatedMatch" 125 | has_match_statement: 126 | is_a: has_mapping_statement 127 | union_of: 128 | - has_exact_match_statement 129 | - has_broad_match_statement 130 | - has_narrow_match_statement 131 | - has_related_match_statement 132 | has_dbxref_statement: 133 | comments: 134 | - sqlview>> SELECT * FROM statements WHERE predicate='oio:hasDbXref' 135 | is_a: has_mapping_statement 136 | classification_rules: 137 | is_a: statements 138 | slot_conditions: 139 | predicate: 140 | equals_string: "oio:hasDbXref" 141 | has_mapping_statement: 142 | is_a: statements 143 | union_of: 144 | - has_match_statement 145 | - has_dbxref_statement 146 | contributor: 147 | is_a: node_to_node_statement 148 | classification_rules: 149 | is_a: statements 150 | slot_conditions: 151 | predicate: 152 | equals_string: "dcterms:contributor" 153 | creator: 154 | is_a: node_to_node_statement 155 | classification_rules: 156 | is_a: statements 157 | slot_conditions: 158 | predicate: 159 | equals_string: "dcterms:creator" 160 | orcid: 161 | is_a: node 162 | slots: 163 | - label 164 | comments: 165 | - |- 166 | sqlview>> 167 | SELECT 168 | subject AS id, 169 | value AS label 170 | FROM 171 | rdfs_label_statement 172 | WHERE subject like 'orcid:%' 173 | 174 | axiom_dbxref_annotation: 175 | comments: 176 | - sqlview>> SELECT * FROM owl_axiom_annotation WHERE annotation_predicate = 'oio:hasDbXref' 177 | is_a: owl_axiom_annotation 178 | classification_rules: 179 | is_a: owl_axiom_annotation 180 | slot_conditions: 181 | annotation_predicate: 182 | equals_string: "oio:hasDbXref" 183 | slots: 184 | evidence_type: 185 | publication: 186 | source: 187 | -------------------------------------------------------------------------------- /src/semsql/linkml/owl.yaml: -------------------------------------------------------------------------------- 1 | name: semsql_owl 2 | description: OWL Module 3 | title: Semantic SQL OWL module 4 | id: https://w3id.org/semsql/owl 5 | imports: 6 | - rdf 7 | license: https://creativecommons.org/publicdomain/zero/1.0/ 8 | prefixes: 9 | semsql: https://w3id.org/semsql/ 10 | linkml: https://w3id.org/linkml/ 11 | default_curi_maps: 12 | - semweb_context 13 | default_prefix: semsql 14 | default_range: string 15 | slots: 16 | restriction: 17 | range: blank_node 18 | on_property: 19 | range: node 20 | filler: 21 | range: class_node 22 | annotation_subject: 23 | is_a: subject 24 | annotation_predicate: 25 | is_a: predicate 26 | annotation_object: 27 | is_a: object 28 | annotation_value: 29 | is_a: value 30 | annotation_language: 31 | is_a: language 32 | annotation_datatype: 33 | is_a: datatype 34 | axiom_predicate: 35 | deprecated: use annotation_* instead 36 | axiom_object: 37 | deprecated: use annotation_* instead 38 | axiom_value: 39 | deprecated: use annotation_* instead 40 | axiom_language: 41 | deprecated: use annotation_* instead 42 | axiom_datatype: 43 | deprecated: use annotation_* instead 44 | classes: 45 | typed_node: 46 | is_a: node 47 | comments: 48 | - sqlview>> SELECT subject AS id, object AS node_type FROM rdf_type_statement 49 | ontology_node: 50 | description: A node representing an ontology 51 | see_also: 52 | - https://www.w3.org/TR/owl2-syntax/#Ontologies 53 | is_a: typed_node 54 | classification_rules: 55 | is_a: typed_node 56 | slot_conditions: 57 | node_type: 58 | equals_string: "owl:Ontology" 59 | typed_property_node: 60 | is_a: property_node 61 | object_property_node: 62 | description: A node representing an OWL object property 63 | see_also: 64 | - https://www.w3.org/TR/owl2-syntax/#Object_Properties 65 | is_a: typed_property_node 66 | classification_rules: 67 | is_a: typed_node 68 | slot_conditions: 69 | node_type: 70 | equals_string: "owl:ObjectProperty" 71 | transitive_property_node: 72 | description: A node representing an OWL transitive object property 73 | is_a: object_property_node 74 | classification_rules: 75 | is_a: typed_node 76 | slot_conditions: 77 | node_type: 78 | equals_string: "owl:TransitiveProperty" 79 | symmetric_property_node: 80 | description: A node representing an OWL symmetric object property 81 | is_a: object_property_node 82 | classification_rules: 83 | is_a: typed_node 84 | slot_conditions: 85 | node_type: 86 | equals_string: "owl:SymmetricProperty" 87 | reflexive_property_node: 88 | description: A node representing an OWL reflexive object property 89 | is_a: object_property_node 90 | classification_rules: 91 | is_a: typed_node 92 | slot_conditions: 93 | node_type: 94 | equals_string: "owl:ReflexiveProperty" 95 | irreflexive_property_node: 96 | description: A node representing an OWL irreflexive object property 97 | is_a: object_property_node 98 | classification_rules: 99 | is_a: typed_node 100 | slot_conditions: 101 | node_type: 102 | equals_string: "owl:IrreflexiveProperty" 103 | asymmetric_property_node: 104 | is_a: object_property_node 105 | classification_rules: 106 | is_a: typed_node 107 | slot_conditions: 108 | node_type: 109 | equals_string: "owl:AsymmetricProperty" 110 | annotation_property_node: 111 | description: A node representing an OWL annotation property 112 | is_a: typed_property_node 113 | classification_rules: 114 | is_a: typed_node 115 | slot_conditions: 116 | node_type: 117 | equals_string: "owl:AnnotationProperty" 118 | deprecated_node: 119 | aliases: 120 | - deprecated IRI 121 | comments: 122 | - sqlview>> SELECT DISTINCT subject AS id FROM statements WHERE predicate='owl:deprecated' 123 | AND value='true' 124 | is_a: node 125 | owl_imports_statement: 126 | comments: 127 | - sqlview>> SELECT * FROM statements WHERE predicate='owl:imports' 128 | is_a: node_to_node_statement 129 | classification_rules: 130 | is_a: statements 131 | slot_conditions: 132 | predicate: 133 | equals_string: "owl:imports" 134 | owl_inverse_of_statement: 135 | comments: 136 | - sqlview>> SELECT * FROM statements WHERE predicate='owl:inverseOf' 137 | is_a: node_to_node_statement 138 | classification_rules: 139 | is_a: statements 140 | slot_conditions: 141 | predicate: 142 | equals_string: "owl:inverseOf" 143 | owl_complement_of_statement: 144 | comments: 145 | - sqlview>> SELECT * FROM statements WHERE predicate='owl:complementOf' 146 | is_a: node_to_node_statement 147 | classification_rules: 148 | is_a: statements 149 | slot_conditions: 150 | predicate: 151 | equals_string: "owl:complementOf" 152 | owl_equivalent_class_statement: 153 | description: A statement that connects two class_nodes where both classes are 154 | equivalent 155 | comments: 156 | - sqlview>> SELECT * FROM statements WHERE predicate='owl:equivalentClass' 157 | is_a: node_to_node_statement 158 | slot_usage: 159 | subject: 160 | description: One of the two classes that are equivalent. No significance to 161 | subject vs object 162 | range: class_node 163 | object: 164 | description: One of the two classes that are equivalent. No significance to 165 | subject vs object 166 | range: class_node 167 | classification_rules: 168 | is_a: statements 169 | slot_conditions: 170 | predicate: 171 | equals_string: "owl:equivalentClass" 172 | owl_same_as_statement: 173 | description: A statement that connects two individual nodes where both individual 174 | are equivalent 175 | is_a: node_to_node_statement 176 | slot_usage: 177 | subject: 178 | description: One of the two classes that are equivalent. No significance to 179 | subject vs object 180 | range: named_individual_node 181 | object: 182 | description: One of the two classes that are equivalent. No significance to 183 | subject vs object 184 | range: named_individual_node 185 | classification_rules: 186 | is_a: statements 187 | slot_conditions: 188 | predicate: 189 | equals_string: "owl:sameAs" 190 | owl_disjoint_class_statement: 191 | is_a: node_to_node_statement 192 | slot_usage: 193 | subject: 194 | description: One of the two classes that are disjoint. No significance to 195 | subject vs object 196 | range: class_node 197 | object: 198 | description: One of the two classes that are disjoint. No significance to 199 | subject vs object 200 | range: class_node 201 | classification_rules: 202 | is_a: statements 203 | slot_conditions: 204 | predicate: 205 | equals_string: "owl:disjointWith" 206 | owl_reified_axiom: 207 | description: An OWL axiom that has been reified - i.e. it includes an [id](id) 208 | field that uniquely identifies that axiom and which can be the subject of additional 209 | statements 210 | comments: 211 | - sqlview>> SELECT axs.subject AS id, axs.stanza AS stanza, axs.object AS subject, 212 | axp.object AS predicate, axo.object AS object, axo.value AS value, axo.datatype 213 | AS datatype, axo.language AS language FROM statements AS axs, statements AS 214 | axp, statements AS axo WHERE axs.predicate = 'owl:annotatedSource' AND axp.predicate 215 | = 'owl:annotatedProperty' AND axo.predicate = 'owl:annotatedTarget' AND axs.subject 216 | = axp.subject AND axs.subject = axo.subject 217 | is_a: statements 218 | slots: 219 | - id 220 | owl_axiom: 221 | comments: 222 | - sqlview>> SELECT * FROM owl_reified_axiom UNION SELECT NULL AS id, * FROM statements 223 | is_a: statements 224 | slots: 225 | - id 226 | owl_axiom_annotation: 227 | comments: 228 | - |- 229 | sqlview>> 230 | SELECT 231 | axpv.stanza AS stanza, 232 | axs.object AS subject, 233 | axp.object AS predicate, 234 | axo.object AS object, 235 | axo.value AS value, 236 | axo.datatype AS datatype, 237 | axo.language AS language, 238 | axpv.subject AS id, 239 | axpv.subject AS annotation_subject, 240 | axpv.predicate AS annotation_predicate, 241 | axpv.object AS annotation_object, 242 | axpv.value AS annotation_value, 243 | axpv.language AS annotation_language, 244 | axpv.datatype AS annotation_datatype 245 | FROM 246 | statements AS axs, 247 | statements AS axp, 248 | statements AS axo, 249 | statements AS axpv 250 | WHERE 251 | axs.predicate = 'owl:annotatedSource' AND 252 | axp.predicate = 'owl:annotatedProperty' AND 253 | axo.predicate = 'owl:annotatedTarget' AND 254 | axs.subject = axpv.subject AND 255 | axp.subject = axpv.subject AND axo.subject = axpv.subject AND 256 | axpv.predicate NOT IN ('owl:annotatedSource', 'owl:annotatedProperty', 'owl:annotatedTarget', 'rdf:type'); 257 | is_a: statements 258 | slots: 259 | - annotation_subject 260 | - annotation_predicate 261 | - annotation_object 262 | - annotation_value 263 | - annotation_language 264 | - annotation_datatype 265 | ## deprecated 266 | - id 267 | anonymous_expression: 268 | description: An OWL expression, such as a class expression. Expressions are "anonymous" 269 | as they are a composition of named elements rather than a named element themselves 270 | is_a: blank_node 271 | abstract: true 272 | anonymous_class_expression: 273 | description: An OWL anonymous class expression, such as for example `SomeValuesFrom(partOf 274 | Hand)` 275 | is_a: anonymous_expression 276 | abstract: true 277 | mixins: 278 | - class_trait 279 | anonymous_property_expression: 280 | is_a: anonymous_expression 281 | abstract: true 282 | mixins: 283 | - property_trait 284 | anonymous_individual_expression: 285 | is_a: anonymous_expression 286 | abstract: true 287 | mixins: 288 | - individual_trait 289 | owl_restriction: 290 | description: An OWL restriction, such as `SomeValuesFrom(partOf Hand)` 291 | is_a: anonymous_class_expression 292 | abstract: true 293 | slots: 294 | - on_property 295 | - filler 296 | slot_usage: 297 | id: 298 | description: the id of the restriction 299 | class_uri: owl:Restriction 300 | owl_some_values_from: 301 | aliases: 302 | - existential restriction 303 | description: An OWL SomeValuesFrom restriction 304 | comments: 305 | - "sqlview>>\n SELECT onProperty.subject AS id,\n onProperty.object\ 306 | \ AS on_property,\n f.object AS filler\n FROM\n \ 307 | \ statements AS onProperty,\n statements AS f\n WHERE\n onProperty.predicate\ 308 | \ = 'owl:onProperty' AND\n onProperty.subject=f.subject AND\n f.predicate='owl:someValuesFrom'" 309 | is_a: owl_restriction 310 | owl_all_values_from: 311 | aliases: 312 | - universal restriction 313 | comments: 314 | - "sqlview>>\n SELECT onProperty.subject AS id,\n onProperty.object\ 315 | \ AS on_property,\n f.object AS filler\n FROM\n \ 316 | \ statements AS onProperty,\n statements AS f\n WHERE\n onProperty.predicate\ 317 | \ = 'owl:onProperty' AND\n onProperty.subject=f.subject AND\n f.predicate='owl:allValuesFrom'" 318 | is_a: owl_restriction 319 | owl_has_value: 320 | aliases: 321 | - value restriction 322 | comments: 323 | - "sqlview>>\n SELECT onProperty.subject AS id,\n onProperty.object\ 324 | \ AS on_property,\n f.object AS filler\n FROM\n \ 325 | \ statements AS onProperty,\n statements AS f\n WHERE\n onProperty.predicate\ 326 | \ = 'owl:onProperty' AND\n onProperty.subject=f.subject AND\n f.predicate='owl:hasValue'" 327 | is_a: owl_restriction 328 | owl_has_self: 329 | aliases: 330 | - self restriction 331 | comments: 332 | - "sqlview>>\n SELECT onProperty.subject AS id,\n onProperty.object\ 333 | \ AS on_property,\n f.object AS filler\n FROM\n \ 334 | \ statements AS onProperty,\n statements AS f\n WHERE\n onProperty.predicate\ 335 | \ = 'owl:onProperty' AND\n onProperty.subject=f.subject AND\n f.predicate='owl:hasSelf'\ 336 | \ AND\n f.value='true'" 337 | is_a: owl_restriction 338 | slot_usage: 339 | filler: 340 | description: This is Null for a self-restriction 341 | owl_complex_axiom: 342 | description: An axiom that is composed of two or more statements 343 | abstract: true 344 | slots: 345 | - subject 346 | - predicate 347 | - object 348 | owl_subclass_of_some_values_from: 349 | description: Composition of subClassOf and SomeValuesFrom 350 | comments: 351 | - "sqlview>>\n SELECT subClassOf.stanza,\n subClassOf.subject,\n \ 352 | \ svf.on_property AS predicate,\n svf.filler AS object\n \ 353 | \ FROM\n statements AS subClassOf, \n owl_some_values_from AS svf\n \ 354 | \ WHERE\n subClassOf.predicate = 'rdfs:subClassOf' AND\n svf.id=subClassOf.object" 355 | is_a: owl_complex_axiom 356 | slots: 357 | - subject 358 | - predicate 359 | - object 360 | slot_usage: 361 | subject: 362 | description: the class C in the axiom C subClassOf P some D 363 | role: subclass 364 | predicate: 365 | description: the predicate P in the axiom C subClassOf P some D 366 | role: on property 367 | object: 368 | description: the class D in the axiom C subClassOf P some D 369 | role: filler 370 | owl_equivalent_to_intersection_member: 371 | description: Composition of `OwlEquivalentClass`, `OwlIntersectionOf`, and `RdfListMember`; 372 | `C = X1 and ... and Xn` 373 | comments: 374 | - "sqlview>>\n SELECT e.stanza,\n e.subject,\n m.object\n\ 375 | \ FROM\n owl_equivalent_class_statement AS e JOIN\n statements\ 376 | \ AS i ON (e.object=i.subject) JOIN\n rdf_list_member_statement AS m ON\ 377 | \ (i.object=m.subject)\n WHERE\n i.predicate = 'owl:intersectionOf'" 378 | is_a: owl_complex_axiom 379 | slots: 380 | - subject 381 | - object 382 | slot_usage: 383 | subject: 384 | description: the defined class 385 | role: subclass 386 | object: 387 | description: a class expression that forms the defining expression 388 | role: filler 389 | source_file: ../semantic-sql/src/linkml/owl.yaml 390 | -------------------------------------------------------------------------------- /src/semsql/linkml/rdf.yaml: -------------------------------------------------------------------------------- 1 | name: rdf 2 | description: Abstractions for working with RDF and RDFS triples 3 | title: Semantic SQL RDF module 4 | id: https://w3id.org/semsql/rdf 5 | imports: 6 | - linkml:types 7 | license: https://creativecommons.org/publicdomain/zero/1.0/ 8 | prefixes: 9 | semsql: https://w3id.org/semsql/ 10 | linkml: https://w3id.org/linkml/ 11 | sh: http://www.w3.org/ns/shacl# 12 | default_curi_maps: 13 | - semweb_context 14 | default_prefix: semsql 15 | default_range: string 16 | subsets: 17 | export: 18 | description: Used to indicate a table/class that should be dumped as part of the 19 | export of a db 20 | base table: 21 | description: Indicates the class/table is typically not constructed from a view 22 | types: 23 | node id type: 24 | description: IDs are either CURIEs, IRI, or blank nodes. IRIs are wrapped in <>s 25 | to distinguish them from CURIEs, but in general it is good practice to populate 26 | the [prefixes][Prefixes.md] table such that they are shortened to CURIEs. Blank 27 | nodes are ids starting with `_:`. 28 | see_also: 29 | - https://github.com/ontodev/rdftab.rs/issues/18 30 | typeof: uriorcurie 31 | literal as string type: 32 | typeof: string 33 | slots: 34 | id: 35 | description: An identifier for an element. Note blank node ids are not unique 36 | across databases 37 | identifier: true 38 | range: node id type 39 | label: 40 | description: A label for an element 41 | range: string 42 | see_also: 43 | - semsql:rdfs_label_statements 44 | comments: 45 | - intended for use as a node property. Labels can also be retrieved as rdfs_label_statement objects 46 | subject: 47 | aliases: 48 | - about 49 | - source 50 | - head 51 | slot_uri: rdf:subject 52 | range: node 53 | description: The subject of the statement 54 | predicate: 55 | aliases: 56 | - relationship type 57 | - property 58 | slot_uri: rdf:predicate 59 | range: property_node 60 | description: The predicate of the statement 61 | object: 62 | aliases: 63 | - target 64 | - sink 65 | - tail 66 | description: Note the range of this slot is always a node. If the triple represents 67 | a literal, instead value will be populated 68 | slot_uri: rdf:object 69 | range: node 70 | graph: 71 | range: node 72 | stanza: 73 | see_also: 74 | - https://github.com/ontodev/rdftab.rs#stanzas 75 | range: node 76 | deprecated: this will disappear in future versions 77 | description: the term which this statement is about 78 | datatype: 79 | description: the rdf datatype of the value, for example, xsd:string 80 | comments: 81 | - only used when value is populated 82 | value: 83 | close_mappings: 84 | - rdf:object 85 | description: Note the range of this slot is always a string. Only used the triple 86 | represents a literal assertion 87 | range: literal as string type 88 | slot_uri: rdf:object 89 | language: 90 | todos: 91 | - use an enum 92 | range: string 93 | description: the human language in which the value is encoded, e.g. 'en' 94 | comments: 95 | - only used when value is populated 96 | prefix: 97 | description: A standardized prefix such as 'GO' or 'rdf' or 'FlyBase' 98 | slot_uri: sh:prefix 99 | range: ncname 100 | base: 101 | description: The base URI a prefix will expand to 102 | slot_uri: sh:namespace 103 | range: uri 104 | local_identifier: 105 | description: The part of a CURIE after the colon 106 | comments: 107 | - for OBOs this is frequently a string of zero-padded digits, but this is not always the case 108 | aliases: 109 | - code 110 | - accession 111 | range: string 112 | description: 113 | slot_uri: dcterms:description 114 | classes: 115 | prefix: 116 | description: Maps CURIEs to URIs 117 | in_subset: 118 | - export 119 | - base table 120 | slots: 121 | - prefix 122 | - base 123 | class_uri: sh:PrefixDeclaration 124 | statements: 125 | aliases: 126 | - triple 127 | description: Represents an RDF triple 128 | in_subset: 129 | - base table 130 | slots: 131 | #- stanza -- DEPRECATED 132 | - subject 133 | - predicate 134 | - object 135 | - value 136 | - datatype 137 | - language 138 | # - graph -- DO NOT INTRODUCE UNTIL SQLA CHANGES ROLLED OUT 139 | class_uri: rdf:Statement 140 | node_to_node_statement: 141 | description: A statement where object is non-null and value is not populated 142 | comments: 143 | - sqlview>> SELECT * FROM statements WHERE object IS NOT NULL 144 | is_a: statements 145 | abstract: true 146 | slot_usage: 147 | object: 148 | required: true 149 | node_to_value_statement: 150 | description: A statement where value is non-null and object is not populated 151 | comments: 152 | - sqlview>> SELECT * FROM statements WHERE value IS NOT NULL 153 | is_a: statements 154 | abstract: true 155 | slot_usage: 156 | value: 157 | required: true 158 | rdf_type_statement: 159 | description: A statement that indicates the asserted type of the subject entity 160 | is_a: node_to_node_statement 161 | slot_usage: 162 | object: 163 | description: The entity type 164 | range: class_node 165 | classification_rules: 166 | is_a: statements 167 | slot_conditions: 168 | predicate: 169 | equals_string: "rdf:type" 170 | rdfs_subclass_of_statement: 171 | is_a: node_to_node_statement 172 | slot_usage: 173 | subject: 174 | description: The subclass element of the triple 175 | range: class_node 176 | object: 177 | description: The superclass element of the triple 178 | range: class_node 179 | classification_rules: 180 | is_a: statements 181 | slot_conditions: 182 | predicate: 183 | equals_string: "rdfs:subClassOf" 184 | rdfs_subclass_of_named_statement: 185 | comments: 186 | - sqlview>> SELECT * FROM rdfs_subclass_of_statement WHERE object NOT LIKE '_:%'; 187 | is_a: rdfs_subclass_of_statement 188 | rdfs_subproperty_of_statement: 189 | is_a: node_to_node_statement 190 | slot_usage: 191 | subject: 192 | description: The subproperty element of the triple 193 | range: property_node 194 | object: 195 | description: The superproperty element of the triple 196 | range: property_node 197 | classification_rules: 198 | is_a: statements 199 | slot_conditions: 200 | predicate: 201 | equals_string: "rdfs:subPropertyOf" 202 | rdfs_label_statement: 203 | description: A statement that connects a node to a human-readable label 204 | is_a: node_to_value_statement 205 | slot_usage: 206 | value: 207 | description: The label value 208 | range: string 209 | classification_rules: 210 | is_a: statements 211 | slot_conditions: 212 | predicate: 213 | equals_string: "rdfs:label" 214 | rdfs_domain_statement: 215 | description: A statement that connects a property to its domain class 216 | is_a: node_to_node_statement 217 | slot_usage: 218 | subject: 219 | role: property 220 | description: The property to which the domain applies 221 | range: property_node 222 | object: 223 | role: domain 224 | description: The domain of the property 225 | range: class_node 226 | classification_rules: 227 | is_a: statements 228 | slot_conditions: 229 | predicate: 230 | equals_string: "rdfs:domain" 231 | rdfs_range_statement: 232 | description: A statement that connects a property to its range class or literal type 233 | is_a: node_to_node_statement 234 | slot_usage: 235 | subject: 236 | role: property 237 | description: The property to which the range applies 238 | range: property_node 239 | object: 240 | role: range 241 | description: The range of the property 242 | range: class_node 243 | classification_rules: 244 | is_a: statements 245 | slot_conditions: 246 | predicate: 247 | equals_string: "rdfs:range" 248 | rdf_list_statement: 249 | description: A statement that is used to represent aspects of RDF lists 250 | is_a: statements 251 | abstract: true 252 | slot_usage: 253 | subject: 254 | description: The rdf:List to which the statement applies 255 | range: rdf_list_node 256 | rdf_first_statement: 257 | description: A statement that connects a list to its first element. This is a 258 | low-level triple, it is unlikely you need to use this directly. It is used to 259 | define rdf_list_member_statement, which is more useful 260 | is_a: rdf_list_statement 261 | classification_rules: 262 | is_a: statements 263 | slot_conditions: 264 | predicate: 265 | equals_string: "rdf:first" 266 | rdf_rest_statement: 267 | description: A statement that connects a list to its remaining elements. This 268 | is a low-level triple, it is unlikely you need to use this directly. It is used 269 | to define rdf_list_member_statement, which is more useful 270 | is_a: rdf_list_statement 271 | classification_rules: 272 | is_a: statements 273 | slot_conditions: 274 | predicate: 275 | equals_string: "rdf:rest" 276 | rdf_rest_transitive_statement: 277 | comments: 278 | - "sqlview>>\n WITH RECURSIVE rdf_rest_transitive_statement\n (\n\ 279 | \ subject, object\n )\n AS\n \ 280 | \ (SELECT subject, object\n FROM rdf_rest_statement\n\ 281 | \ UNION ALL\n SELECT\n rest.subject,\ 282 | \ rest_t.object\n FROM rdf_rest_statement AS rest\n \ 283 | \ JOIN rdf_rest_transitive_statement AS rest_t\n ON rest.object\ 284 | \ = rest_t.subject\n )\n SELECT * FROM rdf_rest_transitive_statement" 285 | is_a: rdf_list_statement 286 | rdf_list_member_statement: 287 | comments: 288 | - "sqlview>>\n SELECT\n rest_t.subject,\n f.object\n FROM rdf_rest_transitive_statement\ 289 | \ AS rest_t JOIN rdf_first_statement AS f ON (rest_t.object = f.subject)\n \ 290 | \ UNION\n SELECT subject,object FROM rdf_first_statement;" 291 | is_a: rdf_list_statement 292 | node: 293 | aliases: 294 | - object 295 | - resource 296 | exact_mappings: 297 | - rdf:Resource 298 | description: The basic unit of representation in an RDF or OWL graph 299 | comments: 300 | - sqlview>> SELECT distinct(subject) AS id FROM statements UNION SELECT distinct(object) 301 | AS id FROM statements WHERE datatype IS NOT NULL 302 | slots: 303 | - id 304 | node_identifier: 305 | comments: 306 | - |- 307 | sqlview>> 308 | SELECT 309 | id AS id, 310 | substr(id,0, instr(id,':')) AS prefix, 311 | substr(id,instr(id,':')+1) AS local_identifier 312 | FROM node 313 | slots: 314 | - id 315 | - prefix 316 | - local_identifier 317 | blank_node: 318 | description: A node with an ID that is not preserved between databases 319 | comments: 320 | - sqlview>> SELECT * FROM node WHERE id LIKE '_:%' 321 | is_a: node 322 | rdf_list_node: 323 | description: A node representing an RDF list. Note that you will not likely need 324 | to use this directly. 325 | # comments: 326 | # - sqlview>> SELECT distinct subject AS id from rdf_type_statement WHERE object 327 | # = 'rdf:List'; 328 | is_a: blank_node 329 | classification_rules: 330 | is_a: rdf_type_statement 331 | slot_conditions: 332 | object: 333 | equals_string: "rdf:List" 334 | iri_node: 335 | aliases: 336 | - IRI 337 | - named entity 338 | comments: 339 | - sqlview>> SELECT * FROM node WHERE id NOT LIKE '_:%' 340 | is_a: node 341 | slot_usage: 342 | id: 343 | pattern: '^_:' 344 | class_node: 345 | description: A node that represents an RDFS/OWL class 346 | comments: 347 | - sqlview>> SELECT distinct subject AS id from rdf_type_statement WHERE object 348 | = 'owl:Class'; 349 | is_a: node 350 | mixins: 351 | - class_trait 352 | class_uri: owl:Class 353 | property_node: 354 | description: Note this only directly classifies nodes asserted to be rdf:Properties 355 | comments: 356 | - sqlview>> SELECT distinct subject AS id from rdf_type_statement WHERE object 357 | = 'owl:Property'; 358 | is_a: node 359 | abstract: true 360 | class_uri: owl:Propery 361 | named_individual_node: 362 | description: A node that represents an OWL Named Individual 363 | comments: 364 | - sqlview>> SELECT distinct subject AS id from rdf_type_statement WHERE object 365 | = 'owl:NamedIndividual'; 366 | is_a: node 367 | class_uri: owl:NamedIndividual 368 | rdf_level_summary_statistic: 369 | description: Abstract grouping for views/classes that provide some kind of count 370 | summary about an individual element 371 | abstract: true 372 | mixins: 373 | - is_report 374 | attributes: 375 | element: 376 | range: node 377 | count_value: 378 | range: integer 379 | count_of_predicates: 380 | description: 'Number of distinct usages of a predicate. NOTE MAY CHANGE: does 381 | not currently count existential usage in OWL' 382 | comments: 383 | - sqlview>> SELECT predicate AS element, count(*) AS count_value FROM statements 384 | GROUP BY predicate ORDER BY count_value DESC 385 | is_a: rdf_level_summary_statistic 386 | count_of_instantiated_classes: 387 | description: Number of distinct instantiations of a class. Note in many OBOs, 388 | classes are not directly instantiated 389 | comments: 390 | - sqlview>> SELECT object AS element, count(*) AS count_value FROM rdf_type_statement 391 | GROUP BY element ORDER BY count_value DESC 392 | is_a: rdf_level_summary_statistic 393 | count_of_subclasses: 394 | description: Number of subclasses for a given class 395 | comments: 396 | - sqlview>> SELECT sc.object AS element, count(distinct sc.subject) AS count_value 397 | FROM rdfs_subclass_of_statement AS sc GROUP BY sc.object ORDER BY count_value 398 | DESC 399 | is_a: rdf_level_summary_statistic 400 | node_trait: 401 | description: abstract groupings/properties for different aspects of the model 402 | abstract: true 403 | mixin: true 404 | class_trait: 405 | is_a: node_trait 406 | mixin: true 407 | property_trait: 408 | is_a: node_trait 409 | mixin: true 410 | individual_trait: 411 | is_a: node_trait 412 | mixin: true 413 | is_report: 414 | description: Used to describe classes/views that have a reporting function 415 | abstract: true 416 | mixin: true 417 | 418 | -------------------------------------------------------------------------------- /src/semsql/linkml/relation_graph.yaml: -------------------------------------------------------------------------------- 1 | name: relation_graph 2 | description: "Module for representing Relation Graphs.\n\nThe core concept in a relation\ 3 | \ graph is an [Edge](Edge.md), which consists of\n\n * [subject](subject.md)\n *\ 4 | \ [predicate](predicate.md)\n * [object](object.md)\n\nWhen mapping from OWL, an\ 5 | \ Edge corresponds to one of:\n\n * `A SubClassOf B`, where `B` is a named class,\ 6 | \ and the predicate is rdfs:subClassOf\n * `A SubClassOf P some B`, and the predicate\ 7 | \ is `P`" 8 | title: Relation Graph module 9 | id: https://w3id.org/semsql/relation_graph 10 | imports: 11 | - rdf 12 | - owl 13 | license: https://creativecommons.org/publicdomain/zero/1.0/ 14 | prefixes: 15 | semsql: https://w3id.org/semsql/ 16 | linkml: https://w3id.org/linkml/ 17 | default_curi_maps: 18 | - semweb_context 19 | default_prefix: semsql 20 | default_range: string 21 | slots: 22 | anchor_object: 23 | range: node 24 | anchor_predicate: {} 25 | secondary_predicate: 26 | range: node 27 | classes: 28 | relation_graph_construct: 29 | description: A construct used as part of a Relation Graph 30 | todos: 31 | - consider moving this to its own module 32 | comments: 33 | - Relation Graphs are complementary abstractions to OWL in which axioms of the 34 | form A SubClassOf R some C form individual edges 35 | abstract: true 36 | slots: 37 | - subject 38 | - predicate 39 | - object 40 | edge: 41 | description: A relation graph edge that connects two entities by a predicate. 42 | Note an edge is distinct from a statement, in that an axiom such as A SubClassOf 43 | R some B is represented as multiple statements, but is a single relation graph 44 | edge 45 | notes: 46 | - this should hold direct edges. See also entailed_edge 47 | comments: 48 | - |- 49 | sqlview>> 50 | SELECT subject, predicate, object 51 | FROM owl_subclass_of_some_values_from 52 | UNION 53 | SELECT subject, predicate, object 54 | FROM rdfs_subclass_of_named_statement 55 | UNION 56 | SELECT subject, predicate, object 57 | FROM rdfs_subproperty_of_statement 58 | UNION 59 | SELECT subject, predicate, object 60 | FROM rdf_type_statement WHERE object IN (SELECT id FROM class_node) 61 | in_subset: 62 | - base table 63 | is_a: relation_graph_construct 64 | slots: 65 | - subject 66 | - predicate 67 | - object 68 | subgraph_query: 69 | description: A subgraph query encompasses as subgraph edge and a seed/anchor object 70 | and seed/anchor predicate 71 | is_a: relation_graph_construct 72 | abstract: true 73 | slots: 74 | - subject 75 | - predicate 76 | - object 77 | - anchor_object 78 | - anchor_predicate 79 | slot_usage: 80 | subject: 81 | description: subject of the subgraph edge 82 | predicate: 83 | description: predicate of the subgraph edge 84 | object: 85 | description: object of the subgraph edge 86 | anchor_object: 87 | description: The entity that is used to seed the graph. The seed entity will 88 | bear some relationship to each subgraph edge; E.g. with an ancestor subgraph 89 | query, all edges will have a subject that descends from the ancestor 90 | anchor_predicate: 91 | description: The predicate that is used to determine if an edge should be 92 | included based on relationship to the anchor_object. 93 | subgraph_edge_by_ancestor: 94 | description: An edge within a subgraph anchored around a set of ancestor terms 95 | comments: 96 | - '{''Example'': ''subgraph under uberon limb "SELECT * FROM sg_edge WHERE anchor_object=\''UBERON:0002101\'';"''}' 97 | - '{''Example'': ''subgraph from part-descendants of uberon limb "SELECT * FROM 98 | sg_edge WHERE anchor_object=\''UBERON:0002101\'' AND ancestor_predicate=\''BFO:0000050\'';"''}' 99 | - "sqlview>>\n SELECT\n edge.*,\n ee.predicate AS anchor_predicate,\n \ 100 | \ ee.object AS anchor_object\n FROM edge JOIN entailed_edge AS ee ON (edge.subject\ 101 | \ = ee.subject)" 102 | is_a: subgraph_query 103 | slots: 104 | - subject 105 | - predicate 106 | - object 107 | - anchor_object 108 | - anchor_predicate 109 | slot_usage: 110 | anchor_object: 111 | description: The ancestor term 112 | anchor_predicate: 113 | description: The entailed predicate that holds between each edge subject and 114 | the ancestor 115 | subgraph_edge_by_descendant: 116 | description: An edge within a subgraph anchored around a set of descendant terms 117 | comments: 118 | - '{''Example'': ''subgraph under uberon limb "SELECT * FROM sg_edge WHERE anchor_object=\''UBERON:0002101\'';"''}' 119 | - "sqlview>>\n SELECT\n edge.*,\n ee.predicate AS anchor_predicate,\n \ 120 | \ ee.subject AS anchor_object\n FROM edge JOIN entailed_edge AS ee ON (edge.subject\ 121 | \ = ee.object)" 122 | is_a: subgraph_query 123 | slots: 124 | - subject 125 | - predicate 126 | - object 127 | - anchor_object 128 | - anchor_predicate 129 | slot_usage: 130 | anchor_object: 131 | description: The descendant term 132 | anchor_predicate: 133 | description: The entailed predicate that holds between the descendant and 134 | each edge subject 135 | subgraph_edge_by_ancestor_or_descendant: 136 | comments: 137 | - sqlview>> SELECT * FROM subgraph_edge_by_ancestor 138 | - sqlview>> SELECT * FROM subgraph_edge_by_descendant 139 | is_a: subgraph_query 140 | subgraph_edge_by_parent: 141 | description: An edge within a subgraph anchored around a set of parent terms 142 | comments: 143 | - "sqlview>>\n SELECT\n edge.*,\n ee.predicate AS anchor_predicate,\n \ 144 | \ ee.object AS anchor_object\n FROM edge JOIN edge AS ee ON (edge.subject\ 145 | \ = ee.subject)" 146 | is_a: subgraph_query 147 | slots: 148 | - subject 149 | - predicate 150 | - object 151 | - anchor_object 152 | - anchor_predicate 153 | slot_usage: 154 | anchor_object: 155 | description: The parent term 156 | anchor_predicate: 157 | description: The entailed predicate that holds between each edge subject and 158 | the parent 159 | subgraph_edge_by_child: 160 | description: An edge within a subgraph anchored around a set of child terms 161 | comments: 162 | - "sqlview>>\n SELECT\n edge.*,\n ee.predicate AS anchor_predicate,\n \ 163 | \ ee.subject AS anchor_object\n FROM edge JOIN edge AS ee ON (edge.subject\ 164 | \ = ee.object)" 165 | is_a: subgraph_query 166 | slots: 167 | - subject 168 | - predicate 169 | - object 170 | - anchor_object 171 | - anchor_predicate 172 | slot_usage: 173 | anchor_object: 174 | description: The child term 175 | anchor_predicate: 176 | description: The entailed predicate that holds between the child and each 177 | edge subject 178 | subgraph_edge_by_self: 179 | description: A special null form of a subgraph query where there is no expansion 180 | comments: 181 | - "sqlview>>\n SELECT\n edge.*,\n edge.predicate AS anchor_predicate,\n\ 182 | \ edge.subject AS anchor_object\n FROM edge" 183 | is_a: subgraph_query 184 | slots: 185 | - subject 186 | - predicate 187 | - object 188 | - anchor_object 189 | - anchor_predicate 190 | entailed_edge: 191 | description: A relation graph edge that is inferred 192 | comments: 193 | - '- It is common to populate this via a procedure external to the database, e.g 194 | balhoff/relation-graph' 195 | in_subset: 196 | - base table 197 | see_also: 198 | - https://github.com/balhoff/relation-graph 199 | is_a: relation_graph_construct 200 | slots: 201 | - subject 202 | - predicate 203 | - object 204 | entailed_subclass_of_edge: 205 | is_a: entailed_edge 206 | slot_usage: 207 | subject: 208 | description: The subclass element of the triple 209 | range: class_node 210 | object: 211 | description: The superclass element of the triple 212 | range: class_node 213 | classification_rules: 214 | is_a: entailed_edge 215 | slot_conditions: 216 | predicate: 217 | equals_string: "rdfs:subClassOf" 218 | entailed_type_edge: 219 | is_a: entailed_edge 220 | slot_usage: 221 | object: 222 | description: The superclass element of the triple 223 | range: class_node 224 | classification_rules: 225 | is_a: entailed_edge 226 | slot_conditions: 227 | predicate: 228 | equals_string: "rdf:type" 229 | entailed_edge_cycle: 230 | description: An edge that composes with another edge to make a cycle 231 | comments: 232 | - |- 233 | sqlview>> 234 | SELECT e.*, e2.predicate AS secondary_predicate 235 | FROM entailed_edge AS e, 236 | entailed_edge AS e2 237 | WHERE e.object = e2.subject AND e2.object=e.subject 238 | is_a: relation_graph_construct 239 | slots: 240 | - subject 241 | - predicate 242 | - object 243 | - secondary_predicate 244 | entailed_edge_same_predicate_cycle: 245 | description: An entailed_edge_cycle over a single predicate 246 | comments: 247 | - "sqlview>>\n SELECT * FROM entailed_edge_cycle WHERE predicate = secondary_predicate" 248 | is_a: entailed_edge_cycle 249 | transitive_edge: 250 | description: A relation graph edge that is formed from a chain of one or more 251 | edges 252 | notes: 253 | - this is equivalent to a property path query in SPARQL 254 | - there may be fewer edges than entailed_edge 255 | comments: 256 | - "sqlview>>\n WITH RECURSIVE transitive_edge\n (\n \ 257 | \ subject, predicate, object, depth\n )\n \ 258 | \ AS\n (SELECT subject, predicate, object, 1\n \ 259 | \ FROM edge\n UNION ALL\n SELECT\n \ 260 | \ e.subject, e.predicate, a.object, a.depth+1\n \ 261 | \ FROM edge AS e\n JOIN transitive_edge AS a\n \ 262 | \ ON e.object = a.subject AND e.predicate = a.predicate\n \ 263 | \ )\n SELECT * FROM transitive_edge" 264 | is_a: relation_graph_construct 265 | slots: 266 | - subject 267 | - predicate 268 | - object 269 | -------------------------------------------------------------------------------- /src/semsql/linkml/ro.yaml: -------------------------------------------------------------------------------- 1 | id: https://w3id.org/semsql/ro 2 | name: ro 3 | title: ro module 4 | description: |- 5 | Module for RO relations 6 | 7 | license: https://creativecommons.org/publicdomain/zero/1.0/ 8 | 9 | prefixes: 10 | RO: http://purl.obolibrary.org/obo/RO_ 11 | BFO: http://purl.obolibrary.org/obo/BFO_ 12 | semsql: https://w3id.org/semsql/ 13 | linkml: https://w3id.org/linkml/ 14 | 15 | default_prefix: semsql 16 | default_range: string 17 | 18 | default_curi_maps: 19 | - semweb_context 20 | 21 | imports: 22 | - rdf 23 | - owl 24 | - relation_graph 25 | 26 | classes: 27 | 28 | part_of_edge: 29 | slot_uri: BFO:0000050 30 | is_a: edge 31 | classification_rules: 32 | is_a: edge 33 | slot_conditions: 34 | predicate: 35 | equals_string: "BFO:0000050" 36 | 37 | has_part_edge: 38 | slot_uri: BFO:0000051 39 | is_a: edge 40 | classification_rules: 41 | is_a: edge 42 | slot_conditions: 43 | predicate: 44 | equals_string: "BFO:0000051" 45 | -------------------------------------------------------------------------------- /src/semsql/linkml/semsql.yaml: -------------------------------------------------------------------------------- 1 | id: https://w3id.org/semsql 2 | name: semsql 3 | title: Semantic SQL 4 | description: |- 5 | A datamodel for RDF, OWL, and OBO Ontologies designed to work harmoniously with SQL databases. 6 | 7 | Note that the primary purpose of this linkml schema is to organize and define SQL VIEWs to 8 | be used on top of a generic SQL database following the rdftab statements schema. 9 | These SQL views are encoded with the `sqlviews>>` tag inside the yaml. 10 | 11 | We use linkml to do this rather than a simple SQL DDL file because linkml gives 12 | us a standard way to do things such as: 13 | 14 | * attach descriptions to each view 15 | * define a data dictionary of all columns used, together with domains/ranges 16 | * modular structure with imports 17 | * the ability to attach rich semantic metadata to each schema element 18 | 19 | Additionally, the framework provides automatic compilation to SQLAlchemy models, 20 | and tools for being able to turn views into indexed tables for efficient querying, 21 | as well as a rich searchable documentation system and other tooling. 22 | 23 | This schema is best browsed online: https://cmungall.github.io/semantic-sql/ 24 | 25 | Note that things are in flux, and there some oddities that need ironed out, see 26 | issues for details. 27 | 28 | See the [github repo](https://github.com/cmungall/semantic-sql) for code to convert 29 | from the linkml yaml into SQL DDL 30 | license: https://creativecommons.org/publicdomain/zero/1.0/ 31 | see_also: 32 | - https://github.com/cmungall/semantic-sql 33 | 34 | prefixes: 35 | semsql: https://w3id.org/semsql/ 36 | linkml: https://w3id.org/linkml/ 37 | 38 | default_prefix: semsql 39 | default_range: string 40 | 41 | default_curi_maps: 42 | - semweb_context 43 | 44 | imports: 45 | - rdf 46 | - owl 47 | - obo 48 | - omo 49 | - relation_graph 50 | - term_associations 51 | -------------------------------------------------------------------------------- /src/semsql/linkml/similarity.yaml: -------------------------------------------------------------------------------- 1 | name: semsql_similarity 2 | description: Module for representing and calculating similarities 3 | title: Semantic similarity module 4 | id: https://w3id.org/semsql/nlp 5 | imports: 6 | - rdf 7 | - relation_graph 8 | license: https://creativecommons.org/publicdomain/zero/1.0/ 9 | prefixes: 10 | semsql_similarity: https://w3id.org/semsql/ 11 | linkml: https://w3id.org/linkml/ 12 | default_curi_maps: 13 | - semweb_context 14 | default_prefix: semsql 15 | default_range: string 16 | 17 | classes: 18 | node_pairwise_similarity: 19 | abstract: true 20 | slots: 21 | - node1 22 | - node2 23 | node_pairwise_graph_similarity: 24 | is_a: node_pairwise_similarity 25 | abstract: true 26 | slots: 27 | - num_ancestors 28 | - predicate1 29 | - predicate2 30 | node_pairwise_overlap: 31 | is_a: node_pairwise_graph_similarity 32 | comments: 33 | - |- 34 | sqlview>> 35 | SELECT 36 | e1.subject AS node1, 37 | e2.subject AS node2, 38 | e1.predicate AS predicate1, 39 | e2.predicate AS predicate2, 40 | COUNT(DISTINCT e1.object) AS num_ancestors 41 | FROM entailed_edge AS e1 AND 42 | entailed_edge AS e2 43 | WHERE e1.object = e2.object 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/semsql/linkml/taxon_constraints.yaml: -------------------------------------------------------------------------------- 1 | name: taxon_constraints 2 | description: Module for representing taxon_constraints. 3 | title: taxon_constraints module 4 | see_also: 5 | - https://github.com/obophenotype/uberon/issues/2137 6 | id: https://w3id.org/semsql/taxon_constraints 7 | imports: 8 | - rdf 9 | - owl 10 | - relation_graph 11 | license: https://creativecommons.org/publicdomain/zero/1.0/ 12 | prefixes: 13 | semsql: https://w3id.org/semsql/ 14 | linkml: https://w3id.org/linkml/ 15 | default_curi_maps: 16 | - semweb_context 17 | default_prefix: semsql 18 | default_range: string 19 | slots: 20 | query_taxon: {} 21 | node_with_constraint: {} 22 | class_with_constraint: {} 23 | classes: 24 | taxon: 25 | comments: 26 | - "sqlview>>\n SELECT DISTINCT(subject) AS id FROM entailed_edge\n WHERE predicate='rdfs:subClassOf'\ 27 | \ AND object='NCBITaxon:1'" 28 | is_a: node 29 | class_to_taxon_edge: 30 | description: An edge that connects a node of interest to an organismal taxon 31 | is_a: edge 32 | abstract: true 33 | slots: 34 | - query_taxon 35 | - node_with_constraint 36 | - class_with_constraint 37 | direct_taxon_constraint: 38 | description: A direct asserted edge that connects a node of interest to a taxon 39 | as part of a taxon constraint 40 | is_a: class_to_taxon_edge 41 | abstract: true 42 | direct_never_in_taxon: 43 | description: A direct taxon constraint that asserts a node is NOT found in the 44 | given taxon 45 | todos: 46 | - add a UNION to capture cases where no encoded as OWL TBox axiom 47 | is_a: direct_taxon_constraint 48 | classification_rules: 49 | is_a: statements 50 | slot_conditions: 51 | predicate: 52 | equals_string: "RO:0002161" 53 | direct_in_taxon: 54 | description: A direct taxon constraint that asserts a node is ONLY found in the 55 | given taxon 56 | comments: 57 | - "sqlview>>\n SELECT subject, predicate, object FROM edge WHERE (predicate =\ 58 | \ 'RO:0002162' or predicate = 'RO:0002160')\n AND subject != 'owl:Nothing'" 59 | is_a: direct_taxon_constraint 60 | inferred_taxon_constraint: 61 | description: A taxon constraint that is inferred 62 | is_a: class_to_taxon_edge 63 | abstract: true 64 | inferred_never_in_taxon_direct: 65 | description: A never_in_taxon constraint that is propagated to descendant nodes 66 | of the constrained class 67 | comments: 68 | - "sqlview>>\n SELECT\n e.subject,\n e.predicate,\n e.object AS node_with_constraint,\n\ 69 | \ te.object AS taxon_with_constraint\n FROM direct_never_in_taxon AS te\n\ 70 | \ INNER JOIN entailed_edge AS e ON (e.object=te.subject)" 71 | is_a: inferred_taxon_constraint 72 | inferred_in_taxon_direct: 73 | description: An in_taxon constraint that is propagated to descendant nodes of 74 | the constrained class 75 | comments: 76 | - "sqlview>>\n SELECT\n e.subject,\n e.predicate,\n e.object AS node_with_constraint,\n\ 77 | \ te.object AS taxon_with_constraint\n FROM direct_in_taxon AS te\n INNER\ 78 | \ JOIN entailed_edge AS e ON (e.object=te.subject)" 79 | is_a: inferred_taxon_constraint 80 | inferred_never_in_taxon_1: 81 | description: An inferred taxon constraint where the subject node is inapplicable 82 | to the taxon by virtue of a inferred_never_in_taxon_direct constraint 83 | comments: 84 | - "sqlview>>\n SELECT ie.*, sc.subject AS query_taxon\n FROM inferred_never_in_taxon_direct\ 85 | \ AS ie\n INNER JOIN entailed_edge AS sc ON (ie.taxon_with_constraint=sc.object)\n\ 86 | \ WHERE sc.predicate='rdfs:subClassOf'" 87 | is_a: inferred_taxon_constraint 88 | inferred_never_in_taxon_2: 89 | description: An inferred taxon constraint where the subject node is inapplicable 90 | to the taxon by virtue of a inferred_in_taxon_direct constraint 91 | comments: 92 | - "sqlview>>\n SELECT ie.*, taxon.id AS query_taxon\n FROM inferred_in_taxon_direct\ 93 | \ AS ie,\n taxon\n WHERE taxon.id NOT IN (\n SELECT sc.subject FROM\ 94 | \ entailed_subclass_of_edge AS sc\n WHERE sc.object=ie.taxon_with_constraint\n\ 95 | \ )" 96 | is_a: inferred_taxon_constraint 97 | inferred_never_in_taxon: 98 | description: An inferred taxon constraint where the subject node is inapplicable 99 | to the taxon by virtue of either a inferred_in_taxon_direct constraint or a 100 | inferred_never_in_taxon_direct constraint 101 | comments: 102 | - "sqlview>>\n SELECT * FROM inferred_never_in_taxon_1 UNION SELECT * FROM inferred_never_in_taxon_2" 103 | is_a: inferred_taxon_constraint 104 | most_specific_inferred_in_taxon: 105 | comments: 106 | - "sqlview>>\n SELECT ct.* FROM inferred_in_taxon_direct AS ct\n WHERE NOT EXISTS\ 107 | \ (\n SELECT\n FROM taxon_with_constraint AS msct,\n entailed_subclass_of_edge\ 108 | \ AS sc\n WHERE sc.object = ie.taxon_with_constraint\n AND sc.subject\ 109 | \ = msct.taxon_with_constraint\n AND sc.subject != sc.object\n AND\ 110 | \ msct.subject = ct.subject\n )" 111 | is_a: inferred_taxon_constraint 112 | -------------------------------------------------------------------------------- /src/semsql/linkml/term_associations.yaml: -------------------------------------------------------------------------------- 1 | name: term-associations 2 | description: >- 3 | Generic datamodel of associations to a term 4 | title: Term Association module 5 | id: https://w3id.org/semsql/term-associations 6 | imports: 7 | - rdf 8 | - owl 9 | - omo 10 | license: https://creativecommons.org/publicdomain/zero/1.0/ 11 | prefixes: 12 | semsql: https://w3id.org/semsql/ 13 | linkml: https://w3id.org/linkml/ 14 | default_curi_maps: 15 | - semweb_context 16 | default_prefix: semsql 17 | default_range: string 18 | classes: 19 | term_association: 20 | description: A minimal datamodel for relating a subject entity to an object term 21 | slots: 22 | - id 23 | - subject 24 | - predicate 25 | - object 26 | - evidence_type 27 | - publication 28 | - source 29 | -------------------------------------------------------------------------------- /src/semsql/loader.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import subprocess 3 | from typing import List 4 | 5 | import click 6 | from sqlalchemy import create_engine 7 | from sqlalchemy.sql import text 8 | 9 | 10 | def get_sqlite_path(url: str) -> str: 11 | if url.startswith("sqlite:///"): 12 | return url.replace("sqlite:///", "") 13 | elif ":" in url: 14 | raise Exception("Only sqlite supported") 15 | else: 16 | return url 17 | 18 | 19 | def load_ddl(con, path: str): 20 | with open(path, "r") as stream: 21 | statement = text("\n".join(stream.readlines())) 22 | print(f"s={statement}") 23 | con.execute(statement) 24 | 25 | 26 | def create_and_load(inputs: List[str], create: bool, url: str) -> None: 27 | db = get_sqlite_path(url) 28 | if create: 29 | subprocess.run(["cat", "prefixes/prefix.sql | sqlite3", db]) 30 | engine = create_engine(url) 31 | with engine.connect() as con: 32 | load_ddl(con, "prefixes/prefix.sql") 33 | # load_rdftab(con, "sql/rdftab.sql") 34 | # load_rdftab(con, "sql_schema/semsql.sql") 35 | for input in inputs: 36 | subprocess.run(["./bin/rdftab", db, input]) 37 | 38 | 39 | @click.command() 40 | @click.option("--url", "-u", help="SQL alchemy URL for db (MUST BE SQLITE)") 41 | @click.option("--db", "-d", help="Path to sqlite db") 42 | @click.option("--create/--no-create", default=True, help="set if db is to be created") 43 | @click.argument("inputs", nargs=-1) 44 | def cli(inputs: List[str], create: bool, db: str, url: str): 45 | """ 46 | Load from OWL 47 | """ 48 | if db is not None: 49 | url = f"sqlite:///{db}" 50 | if url is None: 51 | logging.error("Must pass --db or --url") 52 | create_and_load(inputs, create, url) 53 | 54 | 55 | if __name__ == "__main__": 56 | cli() 57 | -------------------------------------------------------------------------------- /src/semsql/ontlib/README.md: -------------------------------------------------------------------------------- 1 | this sub-package is deprecated and replaced by OAK -------------------------------------------------------------------------------- /src/semsql/ontlib/__init__.py: -------------------------------------------------------------------------------- 1 | from semsql.ontlib.subgraph import extract_subgraph 2 | 3 | __all__ = ["extract_subgraph"] 4 | -------------------------------------------------------------------------------- /src/semsql/ontlib/common_queries.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Dict, List, Optional 3 | 4 | from semsql.sqla.semsql import (HasTextDefinitionStatement, Prefix, 5 | RdfsLabelStatement) 6 | 7 | PREFIX_MAP = Dict[str, str] 8 | CURIE = str 9 | 10 | 11 | def get_prefixes(session) -> PREFIX_MAP: 12 | """ 13 | Get all defined prefix mappings 14 | 15 | :param session: 16 | :return: 17 | """ 18 | return {r.prefix: r.base for r in session.query(Prefix)} 19 | 20 | 21 | def get_label(session, id: CURIE, **args) -> Optional[str]: 22 | """ 23 | fetches the label/name for a term id 24 | 25 | :param session: 26 | :param id: CURIE 27 | :param args: 28 | :return: 29 | """ 30 | return get_single_value(session, id, view=RdfsLabelStatement, **args) 31 | 32 | 33 | def get_text_definition(session, id: CURIE, **args) -> Optional[str]: 34 | """ 35 | Fetch label for an entity 36 | 37 | If >1 found, returns an arbitrary one 38 | If none found, returns None 39 | 40 | Note: it may be slow to call this 1000s of times, consider using a join instead 41 | :param session: 42 | :param id: 43 | :param args: 44 | :return: 45 | """ 46 | return get_single_value(session, id, view=HasTextDefinitionStatement, **args) 47 | 48 | 49 | def get_single_value(session, id: CURIE, view=None, strict=False) -> Optional[str]: 50 | q = session.query(view).where(view.subject == id) 51 | val = None 52 | for s in q.all(): 53 | if not strict: 54 | return s.value 55 | else: 56 | if val is None: 57 | val = s.value 58 | elif val != s.value: 59 | raise Exception(f"Multiple values for {view} where id={id}") 60 | return val 61 | 62 | 63 | def term_search(session, terms: List[str], view=None) -> List[CURIE]: 64 | """ 65 | Maps a list of terms (e.g. query search terms to match labels, or IDs) to a list of IDs 66 | 67 | The intended use for this is to take a user query where a user may want to query 68 | either by IDs, or to provide a list of exact terms (e.g. "forelimb"), or queries 69 | (e.g. "%limb%"), and expand the term list to IDs 70 | 71 | The caller can specify a view, which is the SQLAlchemy model class used in the query; 72 | this should be a subtype of Statements. 73 | 74 | If view is none, then the output simply matches the input (i.e the user knows IDs already) 75 | 76 | :param session: 77 | :param terms: list of query terms 78 | :param view: view to use, e.g. RdfsLabelView 79 | :return: list of IDs from expansion 80 | """ 81 | if view is None: 82 | return terms 83 | ids = set() 84 | for t in terms: 85 | q = session.query(view).filter(view.value.like(t)) 86 | n = 0 87 | for row in q.all(): 88 | ids.add(str(row.subject)) 89 | n += 1 90 | if n == 0: 91 | logging.warning(f"No match for query: {t}") 92 | return list(ids) 93 | -------------------------------------------------------------------------------- /src/semsql/sqla/README.md: -------------------------------------------------------------------------------- 1 | # SQL Alchemy Models 2 | 3 | All python code here is auto-generated from the linkml 4 | 5 | See Makefile for details -------------------------------------------------------------------------------- /src/semsql/sqla/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/INCATools/semantic-sql/fca69e0604f60722a9a4927836dd55e4f88d2f91/src/semsql/sqla/__init__.py -------------------------------------------------------------------------------- /src/semsql/sqla/nlp.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, Index, Table, Text 2 | from sqlalchemy.ext.declarative import declarative_base 3 | from sqlalchemy.sql.sqltypes import NullType 4 | 5 | Base = declarative_base() 6 | metadata = Base.metadata 7 | 8 | 9 | class BlankNode(Base): 10 | __tablename__ = "blank_node" 11 | id = Column(Text, primary_key=True) 12 | 13 | 14 | class ClassNode(Base): 15 | __tablename__ = "class_node" 16 | id = Column(Text, primary_key=True) 17 | 18 | 19 | class CountOfInstantiatedClasses(Base): 20 | __tablename__ = "count_of_instantiated_classes" 21 | element = Column(Text, primary_key=True) 22 | number_of_usages = Column(Text, primary_key=True) 23 | 24 | 25 | class CountOfPredicates(Base): 26 | __tablename__ = "count_of_predicates" 27 | element = Column(Text, primary_key=True) 28 | number_of_usages = Column(Text, primary_key=True) 29 | 30 | 31 | class IriNode(Base): 32 | __tablename__ = "iri_node" 33 | id = Column(Text, primary_key=True) 34 | 35 | 36 | class NamedIndividualNode(Base): 37 | __tablename__ = "named_individual_node" 38 | id = Column(Text, primary_key=True) 39 | 40 | 41 | class Node(Base): 42 | __tablename__ = "node" 43 | id = Column(Text, primary_key=True) 44 | 45 | 46 | class NodeToNodeStatement(Base): 47 | """ 48 | A statement where object is non-null and value is not populated 49 | """ 50 | 51 | __tablename__ = "node_to_node_statement" 52 | stanza = Column(Text, primary_key=True) 53 | subject = Column(Text, primary_key=True) 54 | predicate = Column(Text, primary_key=True) 55 | value = Column(Text, primary_key=True) 56 | datatype = Column(Text, primary_key=True) 57 | language = Column(Text, primary_key=True) 58 | object = Column(Text, primary_key=True) 59 | 60 | 61 | class NodeToValueStatement(Base): 62 | """ 63 | A statement where value is non-null and object is not populated 64 | """ 65 | 66 | __tablename__ = "node_to_value_statement" 67 | stanza = Column(Text, primary_key=True) 68 | subject = Column(Text, primary_key=True) 69 | predicate = Column(Text, primary_key=True) 70 | object = Column(Text, primary_key=True) 71 | datatype = Column(Text, primary_key=True) 72 | language = Column(Text, primary_key=True) 73 | value = Column(Text, primary_key=True) 74 | 75 | 76 | class Prefix(Base): 77 | """ 78 | Maps CURIEs to URIs 79 | """ 80 | 81 | __tablename__ = "prefix" 82 | prefix = Column(Text, primary_key=True) 83 | base = Column(Text, primary_key=True) 84 | 85 | 86 | class ProcessedStatement(Base): 87 | __tablename__ = "processed_statement" 88 | subject = Column(Text, primary_key=True) 89 | predicate = Column(Text, primary_key=True) 90 | value = Column(Text, primary_key=True) 91 | transformation_predicate = Column(Text, primary_key=True) 92 | transformed_value = Column(Text, primary_key=True) 93 | 94 | 95 | class PropertyNode(Base): 96 | """ 97 | Note this only directly classifies nodes asserted to be rdf:Properties 98 | """ 99 | 100 | __tablename__ = "property_node" 101 | id = Column(Text, primary_key=True) 102 | 103 | 104 | class RdfFirstStatement(Base): 105 | """ 106 | A statement that connects a list to its first element. This is a low-level triple, it is unlikely you need to use this directly. It is used to define rdf_list_member_statement, which is more useful 107 | """ 108 | 109 | __tablename__ = "rdf_first_statement" 110 | stanza = Column(Text, primary_key=True) 111 | predicate = Column(Text, primary_key=True) 112 | object = Column(Text, primary_key=True) 113 | value = Column(Text, primary_key=True) 114 | datatype = Column(Text, primary_key=True) 115 | language = Column(Text, primary_key=True) 116 | subject = Column(Text, primary_key=True) 117 | 118 | 119 | class RdfLevelSummaryStatistic(Base): 120 | __tablename__ = "rdf_level_summary_statistic" 121 | element = Column(Text, primary_key=True) 122 | number_of_usages = Column(Text, primary_key=True) 123 | 124 | 125 | class RdfListMemberStatement(Base): 126 | __tablename__ = "rdf_list_member_statement" 127 | stanza = Column(Text, primary_key=True) 128 | predicate = Column(Text, primary_key=True) 129 | object = Column(Text, primary_key=True) 130 | value = Column(Text, primary_key=True) 131 | datatype = Column(Text, primary_key=True) 132 | language = Column(Text, primary_key=True) 133 | subject = Column(Text, primary_key=True) 134 | 135 | 136 | class RdfListNode(Base): 137 | """ 138 | A node representing an RDF list 139 | """ 140 | 141 | __tablename__ = "rdf_list_node" 142 | id = Column(Text, primary_key=True) 143 | 144 | 145 | class RdfListStatement(Base): 146 | """ 147 | A statement that is used to represent aspects of RDF lists 148 | """ 149 | 150 | __tablename__ = "rdf_list_statement" 151 | stanza = Column(Text, primary_key=True) 152 | predicate = Column(Text, primary_key=True) 153 | object = Column(Text, primary_key=True) 154 | value = Column(Text, primary_key=True) 155 | datatype = Column(Text, primary_key=True) 156 | language = Column(Text, primary_key=True) 157 | subject = Column(Text, primary_key=True) 158 | 159 | 160 | class RdfRestStatement(Base): 161 | """ 162 | A statement that connects a list to its remaining elements. This is a low-level triple, it is unlikely you need to use this directly. It is used to define rdf_list_member_statement, which is more useful 163 | """ 164 | 165 | __tablename__ = "rdf_rest_statement" 166 | stanza = Column(Text, primary_key=True) 167 | predicate = Column(Text, primary_key=True) 168 | object = Column(Text, primary_key=True) 169 | value = Column(Text, primary_key=True) 170 | datatype = Column(Text, primary_key=True) 171 | language = Column(Text, primary_key=True) 172 | subject = Column(Text, primary_key=True) 173 | 174 | 175 | class RdfRestTransitiveStatement(Base): 176 | __tablename__ = "rdf_rest_transitive_statement" 177 | stanza = Column(Text, primary_key=True) 178 | predicate = Column(Text, primary_key=True) 179 | object = Column(Text, primary_key=True) 180 | value = Column(Text, primary_key=True) 181 | datatype = Column(Text, primary_key=True) 182 | language = Column(Text, primary_key=True) 183 | subject = Column(Text, primary_key=True) 184 | 185 | 186 | class RdfTypeStatement(Base): 187 | """ 188 | A statement that indicates the asserted type of the subject entity 189 | """ 190 | 191 | __tablename__ = "rdf_type_statement" 192 | stanza = Column(Text, primary_key=True) 193 | subject = Column(Text, primary_key=True) 194 | predicate = Column(Text, primary_key=True) 195 | value = Column(Text, primary_key=True) 196 | datatype = Column(Text, primary_key=True) 197 | language = Column(Text, primary_key=True) 198 | object = Column(Text, primary_key=True) 199 | 200 | 201 | class RdfsDomainStatement(Base): 202 | __tablename__ = "rdfs_domain_statement" 203 | stanza = Column(Text, primary_key=True) 204 | subject = Column(Text, primary_key=True) 205 | predicate = Column(Text, primary_key=True) 206 | value = Column(Text, primary_key=True) 207 | datatype = Column(Text, primary_key=True) 208 | language = Column(Text, primary_key=True) 209 | object = Column(Text, primary_key=True) 210 | 211 | 212 | class RdfsLabelStatement(Base): 213 | __tablename__ = "rdfs_label_statement" 214 | stanza = Column(Text, primary_key=True) 215 | subject = Column(Text, primary_key=True) 216 | predicate = Column(Text, primary_key=True) 217 | object = Column(Text, primary_key=True) 218 | datatype = Column(Text, primary_key=True) 219 | language = Column(Text, primary_key=True) 220 | value = Column(Text, primary_key=True) 221 | 222 | 223 | class RdfsRangeStatement(Base): 224 | __tablename__ = "rdfs_range_statement" 225 | stanza = Column(Text, primary_key=True) 226 | subject = Column(Text, primary_key=True) 227 | predicate = Column(Text, primary_key=True) 228 | value = Column(Text, primary_key=True) 229 | datatype = Column(Text, primary_key=True) 230 | language = Column(Text, primary_key=True) 231 | object = Column(Text, primary_key=True) 232 | 233 | 234 | class RdfsSubclassOfNamedStatement(Base): 235 | __tablename__ = "rdfs_subclass_of_named_statement" 236 | stanza = Column(Text, primary_key=True) 237 | predicate = Column(Text, primary_key=True) 238 | value = Column(Text, primary_key=True) 239 | datatype = Column(Text, primary_key=True) 240 | language = Column(Text, primary_key=True) 241 | subject = Column(Text, primary_key=True) 242 | object = Column(Text, primary_key=True) 243 | 244 | 245 | class RdfsSubclassOfStatement(Base): 246 | __tablename__ = "rdfs_subclass_of_statement" 247 | stanza = Column(Text, primary_key=True) 248 | predicate = Column(Text, primary_key=True) 249 | value = Column(Text, primary_key=True) 250 | datatype = Column(Text, primary_key=True) 251 | language = Column(Text, primary_key=True) 252 | subject = Column(Text, primary_key=True) 253 | object = Column(Text, primary_key=True) 254 | 255 | 256 | class RdfsSubpropertyOfStatement(Base): 257 | __tablename__ = "rdfs_subproperty_of_statement" 258 | stanza = Column(Text, primary_key=True) 259 | predicate = Column(Text, primary_key=True) 260 | value = Column(Text, primary_key=True) 261 | datatype = Column(Text, primary_key=True) 262 | language = Column(Text, primary_key=True) 263 | subject = Column(Text, primary_key=True) 264 | object = Column(Text, primary_key=True) 265 | 266 | 267 | class Statements(Base): 268 | """ 269 | Represents an RDF triple 270 | """ 271 | 272 | __tablename__ = "statements" 273 | stanza = Column(Text, primary_key=True) 274 | subject = Column(Text, primary_key=True) 275 | predicate = Column(Text, primary_key=True) 276 | object = Column(Text, primary_key=True) 277 | value = Column(Text, primary_key=True) 278 | datatype = Column(Text, primary_key=True) 279 | language = Column(Text, primary_key=True) 280 | 281 | 282 | class TextualTransformation(Base): 283 | __tablename__ = "textual_transformation" 284 | subject = Column(Text, primary_key=True) 285 | predicate = Column(Text, primary_key=True) 286 | value = Column(Text, primary_key=True) 287 | -------------------------------------------------------------------------------- /src/semsql/sqlutils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/INCATools/semantic-sql/fca69e0604f60722a9a4927836dd55e4f88d2f91/src/semsql/sqlutils/__init__.py -------------------------------------------------------------------------------- /src/semsql/sqlutils/reportgen.py: -------------------------------------------------------------------------------- 1 | import click 2 | from linkml_runtime import SchemaView 3 | from linkml_runtime.utils.formatutils import underscore 4 | 5 | 6 | @click.command() 7 | @click.option("--limit", "-l", default=20) 8 | @click.argument("inputs", nargs=-1) 9 | def cli(inputs, limit: int): 10 | """ 11 | Generates report queries 12 | """ 13 | for input in inputs: 14 | sv = SchemaView(input) 15 | sv.merge_imports() 16 | schema = sv.schema 17 | print("-- ** REPORTS **") 18 | print(f"-- SCHEMA: {schema.id}") 19 | for cn, c in sv.all_classes().items(): 20 | if c.mixin: 21 | continue 22 | if c.abstract: 23 | continue 24 | slots = sv.class_induced_slots(cn) 25 | if len(slots) > 0: 26 | sql_table = underscore(cn) 27 | print(f"SELECT * FROM {sql_table} LIMIT {limit};") 28 | else: 29 | print(f"-- No slots for {cn}") 30 | 31 | 32 | if __name__ == "__main__": 33 | cli() 34 | -------------------------------------------------------------------------------- /src/semsql/sqlutils/view2table.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | 3 | import click 4 | from linkml_runtime import SchemaView 5 | from linkml_runtime.utils.formatutils import underscore 6 | 7 | from semsql.sqlutils.viewgen import get_viewdef 8 | 9 | # DEPRECATED 10 | 11 | 12 | @click.command() 13 | @click.argument("inputs", nargs=-1) 14 | @click.option("--index/--no-index", default=True, help="Create indexes on each column") 15 | @click.option( 16 | "--name", 17 | "-n", 18 | help="Name of class/view to materialize. If blank, will perform for ALL", 19 | ) 20 | def cli(inputs, name: str, index: bool, combinatorial: bool = False): 21 | """ 22 | Generates a command that turns a view into a table 23 | 24 | See https://github.com/cmungall/semantic-sql/issues/9 25 | 26 | Example usage: 27 | ``` 28 | view2table src/linkml/rdf.yaml -n rdfs_label_statement | sqlite3 db/pato.db 29 | ``` 30 | """ 31 | for input in inputs: 32 | sv = SchemaView(input) 33 | for cn, c in sv.all_classes().items(): 34 | tn = underscore(cn) 35 | if name is None or str(cn) == name or tn == name: 36 | view = get_viewdef(c) 37 | if view is not None: 38 | print(f"DROP VIEW {tn};") 39 | print(f"CREATE TABLE {tn} AS {view};") 40 | if index and not combinatorial: 41 | for sn in sv.class_slots(cn): 42 | colname = underscore(sn) 43 | print(f"CREATE INDEX {tn}_{colname} ON {tn}({colname});") 44 | if combinatorial: 45 | if not index: 46 | raise ValueError("Cannot use combinatorial without index") 47 | s = sv.class_slots(cn) 48 | powerset = itertools.chain.from_iterable( 49 | itertools.combinations(s, r) for r in range(len(s) + 1) 50 | ) 51 | for sns in powerset: 52 | colnames = [underscore(sn) for sn in sns] 53 | colnames_str = "_".join(colnames) 54 | colnames_commasep = ",".join(colnames) 55 | print( 56 | f"CREATE INDEX {tn}_{colnames_str} ON {tn}({colnames_commasep});" 57 | ) 58 | 59 | 60 | if __name__ == "__main__": 61 | cli() 62 | -------------------------------------------------------------------------------- /src/semsql/sqlutils/viewgen.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from typing import TextIO 3 | 4 | import click 5 | from linkml_runtime import SchemaView 6 | from linkml_runtime.linkml_model import ClassDefinition, SchemaDefinition 7 | from linkml_runtime.utils.formatutils import underscore 8 | 9 | VIEW_CODE = "sqlview>>" 10 | 11 | 12 | def get_viewdef(c: ClassDefinition) -> str: 13 | """ 14 | Return all VIEW definitions for a class 15 | :param schema: 16 | :param c: class with potential views definitions 17 | :return: view SQL select clause 18 | """ 19 | views = [] 20 | for cmt in c.comments: 21 | cmt = cmt.strip().rstrip(";") 22 | if cmt.startswith(VIEW_CODE): 23 | views.append(cmt.replace(VIEW_CODE, "").strip()) 24 | if len(views) > 0: 25 | return " UNION ".join(views) 26 | else: 27 | if c.union_of: 28 | return " UNION ".join([f"SELECT * FROM {uc}" for uc in c.union_of]) 29 | elif c.classification_rules: 30 | rule = c.classification_rules[0] 31 | if len(c.classification_rules) > 1: 32 | raise ValueError(f"Max 1 classification rule in {c}") 33 | if rule.is_a is None: 34 | raise NotImplementedError("Expected exactly one is-a, got none") 35 | where = [] 36 | for sn, slot in rule.slot_conditions.items(): 37 | v = slot.equals_string 38 | where.append(f"{sn}='{v}'") 39 | if len(where) == 0: 40 | raise ValueError(f"no WHERE in {rule.slot_conditions}") 41 | v = f'SELECT * FROM {rule.is_a} WHERE {" AND ".join(where)}' 42 | return v 43 | else: 44 | return None 45 | 46 | 47 | def generate_views_from_linkml( 48 | schema: SchemaDefinition, view=True, drop_tables=True, output: TextIO = sys.stdout 49 | ) -> None: 50 | """ 51 | Generates SQL VIEW statements from hints in LinkML linkml 52 | 53 | View hints are encoded in comments section in classes/tables section 54 | :param schema: LinkML linkml containing hints 55 | """ 56 | for cn, c in schema.classes.items(): 57 | viewdef = get_viewdef(c) 58 | sql_table = underscore(cn) 59 | if viewdef is not None: 60 | output.write("\n") 61 | if drop_tables: 62 | output.write(f"DROP TABLE {sql_table};\n") 63 | if view: 64 | output.write(f"CREATE VIEW {sql_table} AS {viewdef};\n") 65 | else: 66 | output.write(f"INSERT INTO {sql_table} AS {viewdef};\n") 67 | elif sql_table == "statements": 68 | output.write("\n") 69 | output.write(f"DROP TABLE {sql_table};\n") 70 | cols = [ 71 | "stanza", 72 | "subject", 73 | "predicate", 74 | "object", 75 | "value", 76 | "datatype", 77 | "language", 78 | ] 79 | cols = [f"{c} TEXT" for c in cols] 80 | output.write(f"CREATE TABLE {sql_table} ({','.join(cols)});\n") 81 | 82 | 83 | @click.command() 84 | @click.argument("inputs", nargs=-1) 85 | @click.option("--view/--no-view", default=True) 86 | @click.option("--mergeimports/--no-mergeimports", default=True) 87 | def cli(inputs, mergeimports: bool, view: bool): 88 | """ 89 | Generates SQL VIEW commands from LinkML schema 90 | """ 91 | for input in inputs: 92 | sv = SchemaView(input) 93 | if mergeimports: 94 | sv.merge_imports() 95 | generate_views_from_linkml(sv.schema, view=view) 96 | 97 | 98 | if __name__ == "__main__": 99 | cli() 100 | -------------------------------------------------------------------------------- /src/semsql/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/INCATools/semantic-sql/fca69e0604f60722a9a4927836dd55e4f88d2f91/src/semsql/utils/__init__.py -------------------------------------------------------------------------------- /src/semsql/utils/makefile_utils.py: -------------------------------------------------------------------------------- 1 | from semsql.builder.registry.registry_schema import Makefile, MakefileRule 2 | 3 | 4 | def makerule_to_string(makerule: MakefileRule) -> str: 5 | """ 6 | Convert a MakeRule to a string 7 | 8 | :param makerule: 9 | :return: 10 | """ 11 | dependencies_str = " ".join(makerule.dependencies) 12 | commands_str = "".join([f"\t{c}\n" for c in makerule.commands]) 13 | as_str = f"{makerule.target}: {dependencies_str}\n{commands_str}" 14 | if makerule.precious: 15 | as_str = f"{as_str}\n.PRECIOUS: {makerule.target}" 16 | return f"{as_str}\n" 17 | 18 | 19 | def makefile_to_string(makefile: Makefile) -> str: 20 | """ 21 | Convert a Makefile to a string 22 | 23 | :param makefile: 24 | :return: 25 | """ 26 | return "\n".join([makerule_to_string(m) for m in makefile.rules]) 27 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/INCATools/semantic-sql/fca69e0604f60722a9a4927836dd55e4f88d2f91/tests/__init__.py -------------------------------------------------------------------------------- /tests/inputs/go-nucleus.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/INCATools/semantic-sql/fca69e0604f60722a9a4927836dd55e4f88d2f91/tests/inputs/go-nucleus.db -------------------------------------------------------------------------------- /tests/inputs/robot-example.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/INCATools/semantic-sql/fca69e0604f60722a9a4927836dd55e4f88d2f91/tests/inputs/robot-example.db -------------------------------------------------------------------------------- /tests/integration/Makefile: -------------------------------------------------------------------------------- 1 | RUN = poetry run 2 | 3 | input/%.owl: 4 | robot merge -I http://purl.obolibrary.org/obo/$*.owl -o $@ 5 | .PRECIOUS: input/%.owl 6 | 7 | fake/%.owl: input/%.owl 8 | cp $< $@ 9 | 10 | input/%.db: input/%.owl 11 | $(RUN) semsql make $@ 12 | 13 | fake/%.db: fake/%.owl 14 | touch conf/bad-prefixes.csv && \ 15 | $(RUN) semsql make $@ -P conf/bad-prefixes.csv 16 | .PRECIOUS: fake/%.db 17 | 18 | fake/%.tree: fake/%.db 19 | runoak -i $< tree -p i,p .all -o $@ 20 | 21 | test-bad-prefix: fake/hsapdv.db 22 | runoak -i $< info i^HsapDvFAKE: | head -5 | grep HsapDvFAKE && echo pass 23 | 24 | input/%.tree: input/%.db 25 | runoak -i $< tree -p i,p .all -o $@ 26 | -------------------------------------------------------------------------------- /tests/integration/README.md: -------------------------------------------------------------------------------- 1 | # integration tests 2 | 3 | These are not currently executed as part of actions, they require installation of RG and rdftab. 4 | 5 | Note that these will be folded into the main unit tests after: https://github.com/INCATools/semantic-sql/issues/41 6 | -------------------------------------------------------------------------------- /tests/outputs/README.md: -------------------------------------------------------------------------------- 1 | generated files go here 2 | -------------------------------------------------------------------------------- /tests/test_builder/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/INCATools/semantic-sql/fca69e0604f60722a9a4927836dd55e4f88d2f91/tests/test_builder/__init__.py -------------------------------------------------------------------------------- /tests/test_builder/test_builder.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | 4 | from semsql.builder import builder 5 | from semsql.builder.registry import path_to_ontology_registry 6 | 7 | cwd = os.path.abspath(os.path.dirname(__file__)) 8 | DB_DIR = os.path.join(cwd, "../inputs") 9 | OUTPUT_DIR = os.path.join(cwd, "../outputs") 10 | SRC_OWL = os.path.join(DB_DIR, "go-nucleus.owl") 11 | SRC_DB = os.path.join(DB_DIR, "go-nucleus.db") 12 | TEST_OWL = os.path.join(OUTPUT_DIR, "go-nucleus-in.owl") 13 | TEST_DB = os.path.join(OUTPUT_DIR, "go-nucleus-in.db") 14 | 15 | 16 | class TestBuilder(unittest.TestCase): 17 | def setUp(self) -> None: 18 | self.registry = str(path_to_ontology_registry()) 19 | 20 | def test_builder(self): 21 | mkfile = builder.compile_registry(self.registry) 22 | print(mkfile) 23 | -------------------------------------------------------------------------------- /tests/test_builder/test_cli.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | from shutil import copyfile 4 | 5 | from click.testing import CliRunner 6 | 7 | from semsql.builder.cli import main 8 | 9 | cwd = os.path.abspath(os.path.dirname(__file__)) 10 | DB_DIR = os.path.join(cwd, "../inputs") 11 | OUTPUT_DIR = os.path.join(cwd, "../outputs") 12 | SRC_OWL = os.path.join(DB_DIR, "go-nucleus.owl") 13 | SRC_DB = os.path.join(DB_DIR, "go-nucleus.db") 14 | TEST_OWL = os.path.join(OUTPUT_DIR, "go-nucleus-in.owl") 15 | TEST_DB = os.path.join(OUTPUT_DIR, "go-nucleus-in.db") 16 | 17 | 18 | class TestCommandLineInterface(unittest.TestCase): 19 | def setUp(self) -> None: 20 | runner = CliRunner(mix_stderr=False) 21 | self.runner = runner 22 | 23 | def test_main_help(self): 24 | result = self.runner.invoke(main, ["--help"]) 25 | out = result.stdout 26 | result.stderr 27 | self.assertIn("query", out) 28 | self.assertIn("make", out) 29 | self.assertIn("download", out) 30 | self.assertEqual(0, result.exit_code) 31 | self.assertEqual(0, self.runner.invoke(main, ["download", "--help"]).exit_code) 32 | self.assertEqual(0, self.runner.invoke(main, ["make", "--help"]).exit_code) 33 | self.assertEqual(0, self.runner.invoke(main, ["query", "--help"]).exit_code) 34 | self.assertEqual( 35 | 0, self.runner.invoke(main, ["view2table", "--help"]).exit_code 36 | ) 37 | 38 | def test_view2table(self): 39 | cases = [ 40 | (["--no-index"], "CREATE TABLE deprecated_node AS SELECT"), 41 | (["edge", "--no-index"], "CREATE TABLE edge AS SELECT"), 42 | (["edge", "--index"], "CREATE INDEX edge_subject"), 43 | (["edge", "--full-index"], "CREATE INDEX edge_subject_predicate_object"), 44 | (["edge", "rdfs_label_statement"], "CREATE TABLE rdfs_label_statement"), 45 | ] 46 | for args, expected in cases: 47 | result = self.runner.invoke(main, ["view2table"] + args) 48 | self.assertIn(expected, result.stdout) 49 | self.assertEqual(0, result.exit_code) 50 | 51 | @unittest.skip("Requires Docker or installing dependencies") 52 | def test_make_db(self): 53 | copyfile(SRC_OWL, TEST_OWL) 54 | result = self.runner.invoke(main, ["make", TEST_DB]) 55 | result.stdout 56 | result.stderr 57 | self.assertEqual(0, result.exit_code) 58 | 59 | @unittest.skip("Requires Docker or installing dependencies") 60 | def test_query(self): 61 | result = self.runner.invoke( 62 | main, ["query", "-i", SRC_OWL, "SELECT * FROM rdfs_label_statement"] 63 | ) 64 | out = result.stdout 65 | result.stderr 66 | self.assertIn("nucleus", out) 67 | self.assertEqual(0, result.exit_code) 68 | -------------------------------------------------------------------------------- /tests/test_ontlib/test_common_queries.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | 4 | from sqlalchemy import create_engine 5 | from sqlalchemy.orm import sessionmaker 6 | 7 | from semsql.ontlib.common_queries import term_search 8 | from semsql.sqla.semsql import RdfsLabelStatement 9 | 10 | cwd = os.path.abspath(os.path.dirname(__file__)) 11 | DB_DIR = os.path.join(cwd, "../inputs") 12 | OUTPUT_DIR = os.path.join(cwd, "../outputs") 13 | 14 | 15 | class CommonQueriesTestCase(unittest.TestCase): 16 | """ 17 | Tests common queries 18 | """ 19 | 20 | def setUp(self): 21 | path = os.path.join(DB_DIR, "go-nucleus.db") 22 | engine = create_engine(f"sqlite:///{path}") 23 | self.session = sessionmaker(bind=engine)() 24 | 25 | def test_common_queries(self): 26 | ids = term_search(self.session, ["%nucleus%"], RdfsLabelStatement) 27 | self.assertIn("GO:0005634", ids) 28 | -------------------------------------------------------------------------------- /tests/test_ontlib/test_subgraph.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | 4 | from sqlalchemy import create_engine 5 | from sqlalchemy.orm import sessionmaker 6 | 7 | from semsql.ontlib import extract_subgraph 8 | from semsql.sqla.relation_graph import (SubgraphEdgeByAncestor, 9 | SubgraphEdgeByDescendant) 10 | 11 | cwd = os.path.abspath(os.path.dirname(__file__)) 12 | DB_DIR = os.path.join(cwd, "../inputs") 13 | OUTPUT_DIR = os.path.join(cwd, "../outputs") 14 | 15 | 16 | class SubgraphTestCase(unittest.TestCase): 17 | """ 18 | Tests subgraph extraction 19 | 20 | https://github.com/cmungall/semantic-sql/issues/5 21 | """ 22 | 23 | def test_subgraph(self): 24 | path = os.path.join(DB_DIR, "go-nucleus.db") 25 | engine = create_engine(f"sqlite:///{path}") 26 | session = sessionmaker(bind=engine)() 27 | edges = extract_subgraph( 28 | session, terms=["CL:0000000"], view=SubgraphEdgeByAncestor 29 | ) 30 | lines = [] 31 | print('graph for descendants of "cell"') 32 | for e in edges: 33 | line = f"{e.subject} {e.predicate} {e.object}" 34 | print(line) 35 | lines.append(line) 36 | assert "GO:0031967 BFO:0000050 GO:0043229" in lines 37 | assert "GO:0031975 rdfs:subClassOf GO:0110165" in lines 38 | 39 | print('graph for ancestors of "nuclear membrane"') 40 | edges = extract_subgraph( 41 | session, terms=["GO:0031965"], view=SubgraphEdgeByDescendant 42 | ) 43 | lines = [] 44 | for e in edges: 45 | line = f"{e.subject} {e.predicate} {e.object}" 46 | print(line) 47 | lines.append(line) 48 | assert "GO:0031967 BFO:0000050 GO:0043229" in lines 49 | assert "GO:0031975 rdfs:subClassOf GO:0110165" in lines 50 | -------------------------------------------------------------------------------- /tests/test_orm/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/INCATools/semantic-sql/fca69e0604f60722a9a4927836dd55e4f88d2f91/tests/test_orm/__init__.py -------------------------------------------------------------------------------- /tests/test_orm/test_basic_sqla.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import unittest 4 | 5 | from sqlalchemy import create_engine 6 | from sqlalchemy.orm import aliased, sessionmaker 7 | 8 | from semsql.sqla.semsql import (NodeIdentifier, OwlAxiomAnnotation, 9 | OwlSomeValuesFrom, RdfsLabelStatement, 10 | RdfsSubclassOfStatement) 11 | 12 | cwd = os.path.abspath(os.path.dirname(__file__)) 13 | DB_DIR = os.path.join(cwd, "../inputs") 14 | OUTPUT_DIR = os.path.join(cwd, "../outputs") 15 | 16 | 17 | class SQLAlchemyTestCase(unittest.TestCase): 18 | def setUp(self) -> None: 19 | path = os.path.join(DB_DIR, "go-nucleus.db") 20 | engine = create_engine(f"sqlite:///{path}") 21 | self.session = sessionmaker(bind=engine)() 22 | 23 | def test_basic_sqla(self): 24 | """ 25 | Tests using SQL Alchemy to join/compose SubClassOf and SomeValuesFrom 26 | """ 27 | session = self.session 28 | logging.info("OWL query:") 29 | # q = session.query(RdfsSubclassOfStatement, OwlSomeValuesFrom).\ 30 | # join(OwlSomeValuesFrom, RdfsSubclassOfStatement.object == OwlSomeValuesFrom.id) 31 | q = session.query(RdfsSubclassOfStatement) 32 | q = q.add_entity(OwlSomeValuesFrom) 33 | q = q.join( 34 | OwlSomeValuesFrom, RdfsSubclassOfStatement.object == OwlSomeValuesFrom.id 35 | ) 36 | 37 | lines = [] 38 | for ax, ex in q.all(): 39 | line = f"{ax.subject} subClassOf {ex.on_property} SOME {ex.filler}" 40 | logging.info(line) 41 | lines.append(line) 42 | assert "GO:0016301 subClassOf BFO:0000050 SOME GO:0016310" in lines 43 | assert len(lines) > 100 44 | assert len(lines) < 200 45 | logging.info("As above, with labels") 46 | subclass_label = aliased(RdfsLabelStatement) 47 | filler_label = aliased(RdfsLabelStatement) 48 | # TODO: improve sqla mappings so this does not need to be as explicit 49 | q = ( 50 | session.query( 51 | RdfsSubclassOfStatement, OwlSomeValuesFrom, subclass_label, filler_label 52 | ) 53 | .join( 54 | OwlSomeValuesFrom, 55 | RdfsSubclassOfStatement.object == OwlSomeValuesFrom.id, 56 | ) 57 | .join( 58 | subclass_label, 59 | subclass_label.subject == RdfsSubclassOfStatement.subject, 60 | ) 61 | .join(filler_label, filler_label.subject == OwlSomeValuesFrom.filler) 62 | ) 63 | lines = [] 64 | for ax, ex, sl, fl in q.all(): 65 | line = f'{ax.subject} "{sl.value}" subClassOf {ex.on_property} SOME {ex.filler} "{fl.value}"' 66 | logging.info(line) 67 | # print(line) 68 | lines.append(line) 69 | assert ( 70 | 'GO:0012505 "endomembrane system" subClassOf BFO:0000051 SOME GO:0005773 "vacuole"' 71 | in lines 72 | ) 73 | 74 | # alternate way of doing the same thing 75 | q = session.query(RdfsSubclassOfStatement) 76 | q = q.add_entity(OwlSomeValuesFrom) 77 | q = q.join( 78 | OwlSomeValuesFrom, RdfsSubclassOfStatement.object == OwlSomeValuesFrom.id 79 | ) 80 | 81 | def add_label(q, join_slot): 82 | entity_label = aliased(RdfsLabelStatement) 83 | q = q.add_entity(entity_label) 84 | q = q.join(entity_label, entity_label.subject == join_slot, isouter=True) 85 | return q 86 | 87 | q = add_label(q, RdfsSubclassOfStatement.subject) 88 | q = add_label(q, OwlSomeValuesFrom.filler) 89 | null = "NONE" 90 | for ax, _ex, sl, _fl in q.all(): 91 | line = f'{ax.subject} "{sl.value if sl else null}"' 92 | ' subClassOf {ex.on_property} SOME {ex.filler} "{fl.value if fl else null}"' 93 | logging.info(line) 94 | # print(line) 95 | lines.append(line) 96 | print(len(lines)) 97 | assert ( 98 | 'GO:0012505 "endomembrane system" subClassOf BFO:0000051 SOME GO:0005773 "vacuole"' 99 | in lines 100 | ) 101 | 102 | def test_axiom_annotation(self): 103 | session = self.session 104 | n = 0 105 | ok1 = False 106 | for row in session.query(OwlAxiomAnnotation).filter( 107 | OwlAxiomAnnotation.subject == "GO:0005575" 108 | ): 109 | logging.info(row) 110 | n += 1 111 | if ( 112 | row.predicate == "IAO:0000115" 113 | and row.value.startswith("A location") 114 | and row.annotation_predicate == "oio:hasDbXref" 115 | and row.annotation_value == "NIF_Subcellular:sao1337158144" 116 | ): 117 | ok1 = True 118 | assert ok1 119 | 120 | def test_node_prefixes(self): 121 | session = self.session 122 | n = 0 123 | for row in session.query(NodeIdentifier).filter(NodeIdentifier.prefix == "RO"): 124 | logging.info(row) 125 | n += 1 126 | assert row.id.startswith("RO:") 127 | self.assertEqual(row.id, row.prefix + ":" + row.local_identifier) 128 | -------------------------------------------------------------------------------- /tests/test_orm/test_crud.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | from shutil import copyfile 4 | 5 | from sqlalchemy import create_engine 6 | from sqlalchemy.orm import sessionmaker 7 | 8 | from semsql.sqla.semsql import Statements 9 | 10 | cwd = os.path.abspath(os.path.dirname(__file__)) 11 | DB_DIR = os.path.join(cwd, "../inputs") 12 | OUTPUT_DIR = os.path.join(cwd, "../outputs") 13 | SRC_DB = os.path.join(DB_DIR, "go-nucleus.db") 14 | TEST_DB = os.path.join(DB_DIR, "go-nucleus-copy.db") 15 | 16 | 17 | class CRUDTestCase(unittest.TestCase): 18 | def test_crud(self): 19 | """ 20 | Test using SQL Alchemy to perform updates 21 | 22 | INCOMPLETE 23 | """ 24 | copyfile(SRC_DB, TEST_DB) 25 | engine = create_engine(f"sqlite:///{TEST_DB}") 26 | session = sessionmaker(bind=engine)() 27 | q = ( 28 | session.query(Statements) 29 | .filter(Statements.predicate == "rdfs:label") 30 | .filter(Statements.value.like("% activity")) 31 | ) 32 | for t in q.all(): 33 | print(f"{t.subject} {t.value}") 34 | # https://github.com/ontodev/rdftab.rs/issues/16 35 | t.value = t.value.replace(" activity", "") 36 | # This needs resolved first: https://github.com/ontodev/rdftab.rs/issues/16 37 | # session.commit() 38 | -------------------------------------------------------------------------------- /tests/test_orm/test_problems.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | 4 | from sqlalchemy import create_engine 5 | from sqlalchemy.orm import sessionmaker 6 | 7 | from semsql.sqla.semsql import AllProblems 8 | 9 | cwd = os.path.abspath(os.path.dirname(__file__)) 10 | DB_DIR = os.path.join(cwd, "../inputs") 11 | OUTPUT_DIR = os.path.join(cwd, "../outputs") 12 | 13 | 14 | class ProblemsTestCase(unittest.TestCase): 15 | def test_problems(self): 16 | """ 17 | Tests a simple robot-report style QC check 18 | """ 19 | path = os.path.join(DB_DIR, "go-nucleus.db") 20 | engine = create_engine(f"sqlite:///{path}") 21 | session = sessionmaker(bind=engine)() 22 | print("OWL query:") 23 | q = session.query(AllProblems) 24 | n = 0 25 | for row in q.all(): 26 | n += 1 27 | print(f"{n}: {row.subject} {row.predicate} {row.value}") 28 | assert n > 0 29 | -------------------------------------------------------------------------------- /tests/test_sqlutils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/INCATools/semantic-sql/fca69e0604f60722a9a4927836dd55e4f88d2f91/tests/test_sqlutils/__init__.py -------------------------------------------------------------------------------- /tests/test_sqlutils/test_generate_views.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | from pathlib import Path 4 | 5 | from linkml_runtime import SchemaView 6 | 7 | from semsql.sqlutils.viewgen import generate_views_from_linkml 8 | 9 | cwd = os.path.abspath(os.path.dirname(__file__)) 10 | SCHEMA_DIR = os.path.join(cwd, "../../src/semsql/linkml") 11 | OUTPUT_DIR = os.path.join(cwd, "../outputs") 12 | DDL_OUT = Path(OUTPUT_DIR) / "test.sql" 13 | 14 | 15 | class ViewTestCase(unittest.TestCase): 16 | def test_create_views(self): 17 | """ 18 | tests generation of SQL VIEWs from LinkML linkml 19 | """ 20 | path = os.path.join(SCHEMA_DIR, "rdf.yaml") 21 | sv = SchemaView(path) 22 | s = sv.schema 23 | # TODO: change this so it returns a string, and check content 24 | with open(DDL_OUT, "w") as stream: 25 | generate_views_from_linkml(s, output=stream) 26 | stream.close() 27 | with open(DDL_OUT) as stream: 28 | lines = stream.readlines() 29 | out = "".join(lines) 30 | self.assertIn( 31 | "CREATE VIEW rdfs_subclass_of_statement AS SELECT * FROM statements WHERE predicate='rdfs:subClassOf'", 32 | out, 33 | ) 34 | self.assertIn( 35 | "CREATE VIEW rdf_type_statement AS SELECT * FROM statements WHERE predicate='rdf:type'", 36 | out, 37 | ) 38 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # Tox (http://tox.testrun.org/) is a tool for running tests 2 | # in multiple virtualenvs. This configuration file will run the 3 | # test suite on all supported python versions. To use it, "pip install tox" 4 | # and then run "tox" from this directory. 5 | 6 | [tox] 7 | isolated_build = true 8 | envlist = 9 | lint 10 | flake8 11 | 12 | [testenv:lint] 13 | deps = 14 | black 15 | isort 16 | skip_install = true 17 | commands = 18 | black . 19 | isort . 20 | description = Run linters. 21 | 22 | [testenv:flake8] 23 | skip_install = true 24 | deps = 25 | flake8<5.0.0 26 | flake8-bandit 27 | flake8-black 28 | flake8-bugbear 29 | flake8-colors 30 | flake8-isort 31 | pep8-naming 32 | commands = 33 | flake8 src/ tests/ 34 | description = Run the flake8 tool with several plugins (bandit, docstrings, import order, pep8 naming). 35 | 36 | ######################### 37 | # Flake8 Configuration # 38 | # (.flake8) # 39 | ######################### 40 | [flake8] 41 | ignore = 42 | BLK100 # can't get black and isort to agree 43 | E203 44 | W503 45 | C901 # needs code change so ignoring for now. 46 | E731 # needs code change so ignoring for now. 47 | S101 # asserts are fine 48 | S106 # flags false positives with test_table_filler 49 | N801 # mixed case is bad but there's a lot of auto-generated code 50 | N815 # same ^ 51 | S404 # Consider possible security implications associated with the subprocess module. 52 | S108 # Probable insecure usage of temp file/directory. 53 | S307 # Use of possibly insecure function - consider using safer ast.literal_eval. 54 | S603 # subprocess call - check for execution of untrusted input. 55 | S607 # Starting a process with a partial executable path ["open" in both cases] 56 | S608 # Possible SQL injection vector through string-based query construction. 57 | B024 # StreamingWriter is an abstract base class, but it has no abstract methods. 58 | # Remember to use @abstractmethod, @abstractclassmethod and/or @abstractproperty decorators. 59 | max-line-length = 120 60 | max-complexity = 13 61 | import-order-style = pycharm 62 | application-import-names = 63 | oaklib 64 | tests 65 | exclude = 66 | datamodels ## datamodels are auto-generated 67 | registry_schema.py ## auto-generated 68 | sqla 69 | -------------------------------------------------------------------------------- /utils/create-semsql-db.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DIR=`dirname "$0"`/.. 4 | 5 | # A POSIX variable 6 | OPTIND=1 # Reset in case getopts has been used previously in the shell. 7 | 8 | # Initialize our own variables: 9 | db="" 10 | verbose=0 11 | 12 | while getopts "h?fvrd:" opt; do 13 | case "$opt" in 14 | h|\?) 15 | show_help 16 | exit 0 17 | ;; 18 | f) force=1 19 | ;; 20 | v) verbose=1 21 | ;; 22 | r) report=1 23 | ;; 24 | d) db=$OPTARG 25 | ;; 26 | esac 27 | done 28 | 29 | shift $((OPTIND-1)) 30 | 31 | [ "${1:-}" = "--" ] && shift 32 | 33 | 34 | if [[ $verbose == 1 ]]; then 35 | echo "verbose=$verbose, db='$db', Files: $@, Path=$DIR" 36 | fi 37 | 38 | if [[ "$db" == "" ]]; then 39 | echo "Must pass --db" 40 | exit -1 41 | fi 42 | 43 | if [[ -f $db ]]; then 44 | if [[ $force == 1 ]]; then 45 | if [[ $verbose == 1 ]]; then 46 | echo "Replacing: $db" 47 | fi 48 | rm $db 49 | else 50 | echo "ERROR: file exists: $db" 51 | echo "Use -f to force replacement" 52 | exit -1 53 | fi 54 | fi 55 | 56 | export PATH="$DIR/bin:$PATH" 57 | 58 | cat $DIR/ddl/semsql.sql | sqlite3 $db 59 | sqlite3 -echo $db -cmd ".mode csv" -cmd ".import $DIR/prefixes/prefixes.csv prefix" "SELECT COUNT(*) FROM prefix" 60 | 61 | echo loading "$@" 62 | if [[ "$@" == "" ]]; then 63 | echo "No files to load; exiting" 64 | exit 0 65 | fi 66 | 67 | for owlf in "$@" 68 | do 69 | if [[ $verbose == 1 ]]; then 70 | echo "Loading: $owlf" 71 | fi 72 | $DIR/bin/rdftab $db < $owlf 73 | echo "Counting statements" 74 | sqlite3 $db "SELECT COUNT(*) FROM statements" 75 | echo "Loading relation-graph inferences" 76 | bn="$(basename $owlf .owl)" 77 | inf_file="inferences/$bn-inf.tsv" 78 | #if [[ ! -f $inf_file ]]; then 79 | # relation-graph --ontology-file $owlf --redundant-output-file $@ --non-redundant-output-file inferences/$*-nr.ttl --property http://purl.obolibrary.org/obo/BFO_0000050 80 | #fi 81 | if [[ -f $inf_file ]]; then 82 | echo "Loading inferences $inf_file" 83 | sqlite3 $db -cmd '.separator "\t"' ".import $inf_file entailed_edge" 84 | sqlite3 $db "SELECT COUNT(*) AS num_edges FROM entailed_edge" 85 | else 86 | echo "No inference file: $inf_file" 87 | echo "TODO: add ability to run command to generate this" 88 | fi 89 | done 90 | echo "## Indexing" 91 | cat $DIR/indexes/indexes.sql | sqlite3 $db 92 | 93 | if [[ $report == 1 ]]; then 94 | echo "## Problems" 95 | sqlite3 $db "SELECT * FROM all_problems" 96 | echo "## Predicates" 97 | sqlite3 $db "SELECT * FROM count_of_predicates" 98 | echo "## Class Count" 99 | sqlite3 $db "SELECT * FROM count_of_instantiated_classes" 100 | echo ## Reports 101 | sqlite3 -cmd ".echo on" -cmd ".headers on" $db < reports/query-semsql.sql > reports/out.txt 2> reports/err.txt 102 | if grep -q "Error" reports/err.txt; then 103 | echo "ERRORS" 104 | cat reports/err.txt 105 | exit -1 106 | else 107 | exit 0 108 | fi 109 | fi 110 | -------------------------------------------------------------------------------- /utils/gaf.header.tsv: -------------------------------------------------------------------------------- 1 | db local_id db_object_symbol qualifiers ontology_class_ref supporting_references evidence_type with_or_from aspect db_object_name db_object_synonyms db_object_type db_object_taxon annotation_date assigned_by annotation_extensions gene_product_form 2 | -------------------------------------------------------------------------------- /utils/gaf2tsv: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | SCRIPTPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" 3 | cat $SCRIPTPATH/gaf.header.tsv 4 | grep -v ^\! $1 5 | -------------------------------------------------------------------------------- /utils/gpi2tsv: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | SCRIPTPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" 3 | cat $SCRIPTPATH/gpi.header.tsv 4 | grep -v ^\! $1 5 | -------------------------------------------------------------------------------- /utils/ncbo2owl.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -np 2 | # See: https://github.com/INCATools/ontology-access-kit/issues/427 3 | s@skos:prefLabel @rdfs:label @; 4 | s@ @rdfs:subClassOf @; 5 | s@umls:cui """(\w+)"""\^\^xsd:string@skos:exactMatch umls:$1@; 6 | s@umls:cui """(\w+)"""\^\^xsd:string@skos:exactMatch umls:$1@; 7 | s@() ()@rdfs:subClassOf [a owl:Restriction; owl:onProperty $1; owl:someValuesFrom $3]@; 8 | -------------------------------------------------------------------------------- /utils/stem.py: -------------------------------------------------------------------------------- 1 | import click 2 | from nltk.stem import PorterStemmer 3 | from nltk.tokenize import word_tokenize 4 | from sqlalchemy import create_engine 5 | from sqlalchemy.orm import sessionmaker 6 | 7 | from src.semsql.sqla import Statements, TextualTransformation 8 | 9 | ps = PorterStemmer() 10 | 11 | 12 | def stem(s: str): 13 | toks = word_tokenize(s) 14 | toks = [ps.stem(tok) for tok in toks] 15 | return " ".join(toks) 16 | 17 | 18 | @click.command() 19 | @click.argument("inputs", nargs=-1) 20 | def cli(inputs): 21 | for db in inputs: 22 | engine = create_engine(f"sqlite:///{db}") 23 | Session = sessionmaker(bind=engine) 24 | session = Session() 25 | for row in ( 26 | session.query(Statements.value) 27 | .where(Statements.value.isnot(None)) 28 | .distinct() 29 | ): 30 | s = row.value 31 | tv = stem(s) 32 | tt = TextualTransformation(subject=s, predicate="stem", value=tv) 33 | session.add(tt) 34 | # print(tt) 35 | # session.flush() 36 | session.commit() 37 | 38 | 39 | if __name__ == "__main__": 40 | cli() 41 | -------------------------------------------------------------------------------- /utils/subgraph: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | eval "$(conda shell.bash hook)" 4 | conda activate semantic-sql 5 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 6 | cd $SCRIPT_DIR/.. 7 | export PYTHONPATH="." 8 | python3 -m semsql.subgraph "$@" 9 | #pipenv run python -m semsql.subgraph "$@" 10 | -------------------------------------------------------------------------------- /utils/table-from-view.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | ## EXAMPLE: 4 | ## table-from-view.pl entailed_subclass_of_edge | sqlite3 db/uberon-ext.db 5 | 6 | my $n = shift @ARGV; 7 | 8 | print "CREATE TABLE tmp_$n AS SELECT * FROM $n; "; 9 | print "DROP VIEW $n; "; 10 | print "CREATE TABLE $n AS SELECT * FROM tmp_$n; "; 11 | print "\n"; 12 | 13 | -------------------------------------------------------------------------------- /utils/vsubgraph: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | subgraph -f viz -V up -s conf/obograph-style.json "$@" 3 | -------------------------------------------------------------------------------- /views/bfo.sql: -------------------------------------------------------------------------------- 1 | CREATE VIEW formal_class AS 2 | SELECT 3 | 'BFO' AS ont, id 4 | FROM 5 | node 6 | WHERE 7 | id LIKE 'BFO:%' 8 | UNION 9 | SELECT 10 | 'OGMS' AS ont, id 11 | FROM 12 | node 13 | WHERE 14 | id LIKE 'OGMS:%'; 15 | 16 | CREATE VIEW subclass_of_formal_class AS 17 | SELECT 18 | f.ont, s.* 19 | FROM 20 | rdfs_subclass_of_statement s, 21 | formal_class f 22 | WHERE 23 | s.object=f.id AND s.subject NOT IN (select id FROM formal_class); 24 | 25 | CREATE VIEW formal_class_subclass_count_by_prefix AS 26 | SELECT 27 | s.object AS formal_class_id, ni.prefix, count(distinct s.subject) AS subclass_count 28 | FROM 29 | subclass_of_formal_class AS s, 30 | node_identifier AS ni 31 | WHERE 32 | s.subject=ni.id 33 | GROUP BY formal_class_id, ni.prefix; 34 | 35 | 36 | CREATE VIEW formal_class_subclass_count AS 37 | SELECT 38 | object AS formal_class_id, count(distinct subject) AS subclass_count 39 | FROM 40 | subclass_of_formal_class 41 | GROUP BY formal_class_id; 42 | 43 | CREATE VIEW formal_ontology_usage_count AS 44 | SELECT 45 | ont, count(distinct subject) AS subclass_count 46 | FROM 47 | subclass_of_formal_class 48 | GROUP BY ont; 49 | 50 | CREATE VIEW inter_sofc_edge AS 51 | SELECT 52 | e.*, 53 | s.object AS subject_fc, 54 | o.object AS object_fc 55 | FROM 56 | edge AS e, 57 | subclass_of_formal_class AS s, 58 | subclass_of_formal_class AS o 59 | WHERE 60 | e.subject=s.subject AND 61 | e.object=o.subject; 62 | 63 | CREATE VIEW intra_inter_sofc_edge AS 64 | SELECT 65 | e.*, 66 | nis.prefix as subject_prefix, 67 | ois.prefix as object_prefix 68 | FROM 69 | inter_sofc_edge AS e, 70 | node_identifier AS nis, 71 | node_identifier AS nio 72 | WHERE 73 | e.subject=nis.id AND 74 | e.object=nio.id; 75 | 76 | 77 | --------------------------------------------------------------------------------