Compare commits

...

7 Commits

29 changed files with 823 additions and 2533 deletions

View File

@ -1,72 +0,0 @@
name: Commit Validation
on:
push:
branches:
- "**"
paths-ignore:
- "docs/**"
pull_request:
branches:
- "**"
paths-ignore:
- "docs/**"
workflow_dispatch:
jobs:
formatting:
if: ${{ (github.event_name != 'pull_request') || (github.event.pull_request.head.repo.fork) }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: 20
- run: npm ci
- run: npm run lint
- run: npm run format:ci
build:
if: ${{ (github.event_name != 'pull_request') || (github.event.pull_request.head.repo.fork) }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: 20
- run: npm ci
- run: npm run compile
- name: Upload build
uses: actions/upload-artifact@v4
with:
name: build
path: build/
retention-days: 1
tests-linux:
needs: build
strategy:
fail-fast: false
matrix:
node-version: [18, 20]
uses: ./.github/workflows/tests-linux.yml
with:
node-version: ${{matrix.node-version}}
tests-windows:
needs: build
uses: ./.github/workflows/tests-windows.yml
with:
node-version: 20
# Run with most databases possible to provide the coverage of the tests
coverage:
runs-on: ubuntu-latest
needs: [tests-linux, tests-windows]
steps:
- name: Coveralls Finished
uses: coverallsapp/github-action@v2
with:
parallel-finished: true

View File

@ -6,15 +6,14 @@ on:
- master
jobs:
index_docs:
index-docs:
if: ${{ !github.event.repository.fork }}
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v5
- uses: actions/checkout@v5
- name: Index docs to Typesense
run: |
- run: |
docker run \
-e TYPESENSE_API_KEY=${{ secrets.TYPESENSE_API_KEY }} \
-e TYPESENSE_HOST="${{ secrets.TYPESENSE_HOST }}" \

View File

@ -22,7 +22,7 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v5
with:
node-version: 20
node-version-file: .nvmrc
- name: Install dependencies
run: npm ci

View File

@ -14,7 +14,7 @@ jobs:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: 20
node-version-file: .nvmrc
registry-url: "https://registry.npmjs.org"
- run: npm ci
- run: npm run package

View File

@ -70,12 +70,13 @@ jobs:
services:
mssql:
image: "mcr.microsoft.com/mssql/server:2022-latest"
image: "mcr.microsoft.com/mssql/server:2025-latest"
ports:
- "1433:1433"
env:
SA_PASSWORD: "Admin12345"
ACCEPT_EULA: "Y"
MSSQL_PID: "Express"
steps:
- uses: actions/checkout@v5

127
.github/workflows/tests.yml vendored Normal file
View File

@ -0,0 +1,127 @@
name: Tests
on:
pull_request:
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
changes: ${{ steps.detect-changes.outputs.changes }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: detect-changes
with:
filters: |
package-json: &package-json
- package.json
- package-lock.json
docs: &docs
- docs/**/*
src: &src
- extra/**/*.js
- src/**/*.ts
- gulpfile.js
- tsconfig.json
- *package-json
src-or-tests: &src-or-tests
- *src
- .github/workflows/test/**/*
- .github/workflows/test*.yml
- .mocharc.json
- .nvmrc
- .nycrc.json
lint: &lint
- *src-or-tests
- .prettierrc.json
- eslint.config.mjs
formatting:
if: contains(needs.detect-changes.outputs.changes, 'lint')
needs: detect-changes
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version-file: .nvmrc
- run: npm ci
- run: npm run lint
- run: npm run format:ci
docs:
if: contains(needs.detect-changes.outputs.changes, 'docs')
needs: detect-changes
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./docs
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version-file: .nvmrc
- run: npm ci
- run: npm run build
build:
if: contains(needs.detect-changes.outputs.changes, 'src-or-tests')
needs: detect-changes
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version-file: .nvmrc
- run: npm ci
- run: npm run compile
- uses: actions/upload-artifact@v4
with:
name: build
path: build/
retention-days: 1
tests-linux:
if: contains(needs.detect-changes.outputs.changes, 'src-or-tests')
needs: [detect-changes, build]
strategy:
fail-fast: false
matrix:
node-version: [18, 20]
uses: ./.github/workflows/tests-linux.yml
with:
node-version: ${{matrix.node-version}}
tests-windows:
if: contains(needs.detect-changes.outputs.changes, 'src-or-tests')
needs: [detect-changes, build]
uses: ./.github/workflows/tests-windows.yml
with:
node-version: 20
coverage:
if: contains(needs.detect-changes.outputs.changes, 'src-or-tests')
runs-on: ubuntu-latest
needs: [detect-changes, tests-linux, tests-windows]
steps:
- uses: coverallsapp/github-action@v2
with:
parallel-finished: true
all-passed:
runs-on: ubuntu-latest
if: always()
needs:
- build
- coverage
- docs
- formatting
- tests-linux
- tests-windows
steps:
- run: exit ${{ contains(needs.*.result, 'failure') && 1 || 0 }}

1
.nvmrc Normal file
View File

@ -0,0 +1 @@
20

View File

@ -50,7 +50,17 @@ cd typeorm
git remote add upstream https://github.com/typeorm/typeorm.git
```
## Installing NPM Modules
## Node
You should have node installed in the version described in [.nvmrc](.nvmrc).
It is recommended to configure your OS to automatically switch to use this version whenever you enter project folder. This can be achieved in many ways:
* [`fnm`](https://github.com/Schniz/fnm)
* [`zsh-nvm`](https://github.com/lukechilds/zsh-nvm#auto-use)
* [`asdf`](https://asdf-vm.com) with `asdf-nodejs` plugin and [`legacy_version_file = true`](https://asdf-vm.com/manage/configuration.html#legacy-version-file) option
## Installing package dependencies
Install all TypeORM dependencies by running this command:
@ -89,7 +99,7 @@ You can copy this tar into your project and run `npm install ./typeorm-x.x.x.tgz
## Running Tests Locally
It is greatly appreciated if PRs that change code come with appropriate tests.
It is greatly appreciated if PRs that change code come with appropriate tests.
To create a new test, check the [relevant functional tests](https://github.com/typeorm/typeorm/tree/master/test/functional). Depending on the test, you may need to create a new test file or modify an existing one.

File diff suppressed because it is too large Load Diff

View File

@ -207,12 +207,28 @@ This project exists thanks to all the people who contribute:
## Sponsors
Open source is hard and time-consuming. If you want to invest in TypeORM's future, you can become a sponsor and allow our core team to spend more time on TypeORM's improvements and new features. [Become a sponsor](https://opencollective.com/typeorm)
Open source is hard and time-consuming. If you want to invest in TypeORM's future, you can become a sponsor and allow our core team to spend more time on TypeORM's improvements and new features.
<a href="https://opencollective.com/typeorm" target="_blank"><img src="https://opencollective.com/typeorm/tiers/sponsor.svg?width=890"></a>
### Champion
## Gold Sponsors
Become a champion sponsor and get premium technical support from our core contributors. [Become a champion](https://opencollective.com/typeorm)
Become a gold sponsor and get premium technical support from our core contributors. [Become a gold sponsor](https://opencollective.com/typeorm)
<a href="https://opencollective.com/typeorm" target="_blank"><img src="https://opencollective.com/typeorm/tiers/gold-sponsor.svg?avatarHeight=36"></a>
<a href="https://opencollective.com/typeorm" target="_blank"><img src="https://opencollective.com/typeorm/tiers/gold-sponsor.svg?width=890"></a>
### Supporter
Support TypeORM's development with a monthly contribution. [Become a supporter](https://opencollective.com/typeorm)
<a href="https://opencollective.com/typeorm" target="_blank"><img src="https://opencollective.com/typeorm/tiers/love.svg?avatarHeight=36"></a>
### Community
Join our community of supporters and help sustain TypeORM. [Become a community supporter](https://opencollective.com/typeorm)
<a href="https://opencollective.com/typeorm" target="_blank"><img src="https://opencollective.com/typeorm/tiers/like.svg?avatarHeight=36"></a>
### Sponsor
Make a one-time or recurring contribution of your choice. [Become a sponsor](https://opencollective.com/typeorm)
<a href="https://opencollective.com/typeorm" target="_blank"><img src="https://opencollective.com/typeorm/tiers/sponsor.svg?avatarHeight=36"></a>

File diff suppressed because it is too large Load Diff

View File

@ -73,13 +73,14 @@ services:
# mssql
mssql:
image: "mcr.microsoft.com/mssql/server:2022-latest"
image: "mcr.microsoft.com/mssql/server:2025-latest"
container_name: "typeorm-mssql"
ports:
- "1433:1433"
environment:
SA_PASSWORD: "Admin12345"
ACCEPT_EULA: "Y"
MSSQL_PID: "Express"
# cockroachdb
cockroachdb:

View File

@ -165,4 +165,64 @@ Based on [tedious](https://tediousjs.github.io/node-mssql/) MSSQL implementation
## Column Types
`int`, `bigint`, `bit`, `decimal`, `money`, `numeric`, `smallint`, `smallmoney`, `tinyint`, `float`, `real`, `date`, `datetime2`, `datetime`, `datetimeoffset`, `smalldatetime`, `time`, `char`, `varchar`, `text`, `nchar`, `nvarchar`, `ntext`, `binary`, `image`, `varbinary`, `hierarchyid`, `sql_variant`, `timestamp`, `uniqueidentifier`, `xml`, `geometry`, `geography`, `rowversion`
`int`, `bigint`, `bit`, `decimal`, `money`, `numeric`, `smallint`, `smallmoney`, `tinyint`, `float`, `real`, `date`, `datetime2`, `datetime`, `datetimeoffset`, `smalldatetime`, `time`, `char`, `varchar`, `text`, `nchar`, `nvarchar`, `ntext`, `binary`, `image`, `varbinary`, `hierarchyid`, `sql_variant`, `timestamp`, `uniqueidentifier`, `xml`, `geometry`, `geography`, `rowversion`, `vector`
### Vector Type (vector)
The `vector` data type is available in SQL Server for storing high-dimensional vectors, commonly used for:
- Semantic search with embeddings
- Recommendation systems
- Similarity matching
- Machine learning applications
NOTE: general `halfvec` type support is unavailable because this feature is still in preview. See the Microsoft docs: [Vector data type](https://learn.microsoft.com/en-us/sql/t-sql/data-types/vector-data-type).
#### Usage
```typescript
@Entity()
export class DocumentChunk {
@PrimaryGeneratedColumn()
id: number
@Column("varchar")
content: string
// Vector column with 1998 dimensions
@Column("vector", { length: 1998 })
embedding: number[]
}
```
#### Vector Similarity Search
SQL Server provides the `VECTOR_DISTANCE` function for calculating distances between vectors:
```typescript
const queryEmbedding = [
/* your query vector */
]
const results = await dataSource.query(
`
DECLARE @question AS VECTOR (1998) = @0;
SELECT TOP (10) dc.*,
VECTOR_DISTANCE('cosine', @question, embedding) AS distance
FROM document_chunk dc
ORDER BY VECTOR_DISTANCE('cosine', @question, embedding)
`,
[JSON.stringify(queryEmbedding)],
)
```
**Distance Metrics:**
- `'cosine'` - Cosine distance (most common for semantic search)
- `'euclidean'` - Euclidean (L2) distance
- `'dot'` - Negative dot product
**Requirements:**
- SQL Server version with vector support enabled
- Vector dimensions must be specified using the `length` option

View File

@ -182,16 +182,17 @@ There are several special column types with additional functionality available:
### Vector columns
Vector columns are supported on both PostgreSQL (via [`pgvector`](https://github.com/pgvector/pgvector) extension) and SAP HANA Cloud, enabling storing and querying vector embeddings for similarity search and machine learning applications.
Vector columns are supported on PostgreSQL (via [`pgvector`](https://github.com/pgvector/pgvector) extension), Microsoft SQL Server, and SAP HANA Cloud, enabling storing and querying vector embeddings for similarity search and machine learning applications.
TypeORM supports both `vector` and `halfvec` column types across databases:
- `vector` - stores vectors as 4-byte floats (single precision)
- PostgreSQL: native `vector` type via pgvector extension
- SAP HANA: alias for `real_vector` type
- `halfvec` - stores vectors as 2-byte floats (half precision) for memory efficiency
- PostgreSQL: native `halfvec` type via pgvector extension
- SAP HANA: alias for `half_vector` type
- `vector` - stores vectors as 4-byte floats (single precision)
- PostgreSQL: native `vector` type via pgvector extension
- SQL Server: native `vector` type
- SAP HANA: alias for `real_vector` type
- `halfvec` - stores vectors as 2-byte floats (half precision) for memory efficiency
- PostgreSQL: native `halfvec` type via pgvector extension
- SAP HANA: alias for `half_vector` type
You can specify the vector dimensions using the `length` option:
@ -201,45 +202,65 @@ export class Post {
@PrimaryGeneratedColumn()
id: number
// Vector without specified dimensions (works on PostgreSQL and SAP HANA)
// Vector without specified dimensions (works on PostgreSQL and SAP HANA; SQL Server requires explicit dimensions)
@Column("vector")
embedding: number[] | Buffer
// Vector with 3 dimensions: vector(3) (works on PostgreSQL and SAP HANA)
// Vector with 3 dimensions: vector(3)
@Column("vector", { length: 3 })
embedding_3d: number[] | Buffer
// Half-precision vector with 4 dimensions: halfvec(4) (works on PostgreSQL and SAP HANA)
// Half-precision vector with 4 dimensions: halfvec(4) (PostgreSQL and SAP HANA only)
@Column("halfvec", { length: 4 })
halfvec_embedding: number[] | Buffer
}
```
Vector columns can be used for similarity searches using PostgreSQL's vector operators:
**PostgreSQL** - Vector columns can be used for similarity searches using vector operators:
```typescript
// L2 distance (Euclidean) - <->
const results = await dataSource.query(
`SELECT id, embedding FROM post ORDER BY embedding <-> $1 LIMIT 5`,
["[1,2,3]"]
["[1,2,3]"],
)
// Cosine distance - <=>
const results = await dataSource.query(
`SELECT id, embedding FROM post ORDER BY embedding <=> $1 LIMIT 5`,
["[1,2,3]"]
["[1,2,3]"],
)
// Inner product - <#>
const results = await dataSource.query(
`SELECT id, embedding FROM post ORDER BY embedding <#> $1 LIMIT 5`,
["[1,2,3]"]
["[1,2,3]"],
)
```
> **Note**:
> - **PostgreSQL**: Vector columns require the `pgvector` extension to be installed. The extension provides the vector data types and similarity operators.
> - **SAP HANA**: Vector columns require SAP HANA Cloud (2024Q1+) and a supported version of `@sap/hana-client`. Use the appropriate [vector similarity functions](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-sql-reference-guide/vector-functions) for similarity searches.
**SQL Server** - Use the `VECTOR_DISTANCE` function for similarity searches:
```typescript
const queryEmbedding = [1, 2, 3]
// Cosine distance
const results = await dataSource.query(
`
DECLARE @question AS VECTOR(3) = @0;
SELECT TOP (5) id, embedding,
VECTOR_DISTANCE('cosine', @question, embedding) AS distance
FROM post
ORDER BY VECTOR_DISTANCE('cosine', @question, embedding)
`,
[JSON.stringify(queryEmbedding)],
)
```
> **Note**:
>
> - **PostgreSQL**: Vector columns require the `pgvector` extension to be installed. The extension provides the vector data types and similarity operators.
> - **SQL Server**: Vector type support requires a compatible SQL Server version with vector functionality enabled.
> - **SAP HANA**: Vector columns require SAP HANA Cloud (2024Q1+) and a supported version of `@sap/hana-client`. Use the appropriate [vector similarity functions](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-sql-reference-guide/vector-functions) for similarity searches.
## Column types

View File

@ -34,7 +34,7 @@ TypeORM is highly influenced by other ORMs, such as [Hibernate](http://hibernate
- Cascades.
- Indices.
- Transactions.
- [Migrations](../migrations/01-why.md) with automatic generation.
- [Migrations](/docs/migrations/why) with automatic generation.
- Connection pooling.
- Replication.
- Using multiple database instances.
@ -1195,7 +1195,7 @@ There are several extensions that simplify working with TypeORM and integrating
- ER Diagram generator - [typeorm-uml](https://github.com/eugene-manuilov/typeorm-uml/)
- another ER Diagram generator - [erdia](https://www.npmjs.com/package/erdia/)
- Create, drop and seed database - [typeorm-extension](https://github.com/tada5hi/typeorm-extension)
- Automatically update `data-source.ts` after generating [migrations](../migrations/01-why.md)/entities - [typeorm-codebase-sync](https://www.npmjs.com/package/typeorm-codebase-sync)
- Automatically update `data-source.ts` after generating [migrations](/docs/migrations/why)/entities - [typeorm-codebase-sync](https://www.npmjs.com/package/typeorm-codebase-sync)
- Easy manipulation of `relations` objects - [typeorm-relations](https://npmjs.com/package/typeorm-relations)
- Automatically generate `relations` based on a GraphQL query - [typeorm-relations-graphql](https://npmjs.com/package/typeorm-relations-graphql)

View File

@ -101,7 +101,7 @@ export const redirects = [
},
{ from: "/data-source-api", to: "/docs/data-source/data-source-api" },
{ from: "/migrations", to: "/docs/migrations" },
{ from: "/migrations", to: "/docs/migrations/why" },
{ from: "/transactions", to: "/docs/advanced-topics/transactions" },
{ from: "/indices", to: "/docs/advanced-topics/indices" },
{
@ -123,5 +123,5 @@ export const redirects = [
{ from: "/getting-started", to: "/docs/getting-started" },
{ from: "/future-of-typeorm", to: "/docs/future-of-typeorm" },
{ from: "/query-runner", to: "/docs/query-runner" },
{ from: "/docs/advanced-topics/migrations", to: "/docs/migrations" },
{ from: "/docs/advanced-topics/migrations", to: "/docs/migrations/why" },
]

View File

@ -0,0 +1,96 @@
# Vector Type Support in SQL Server
This sample demonstrates how to use the `vector` column type in SQL Server with TypeORM for storing and querying vector embeddings.
## Overview
SQL Server supports the `vector` data type for storing high-dimensional vectors, which is useful for:
- Semantic search with embeddings
- Recommendation systems
- Similarity matching
- Machine learning applications
## Features Demonstrated
1. **Vector Column Definition**: Define columns with specific vector dimensions
2. **Storing Embeddings**: Save vector data as arrays of numbers
3. **Vector Similarity Search**: Use `VECTOR_DISTANCE` function for cosine similarity
## Entity Definition
```typescript
@Entity("document_chunks")
export class DocumentChunk {
@PrimaryGeneratedColumn("uuid")
id: string
@Column("varchar", { length: "MAX" })
content: string
// Vector column with 1998 dimensions
@Column("vector", { length: 1998 })
embedding: number[]
@Column("uuid")
documentId: string
@ManyToOne(() => Document, (document) => document.chunks)
@JoinColumn({ name: "documentId" })
document: Document
}
```
## Vector Similarity Search
SQL Server provides the `VECTOR_DISTANCE` function for calculating distances between vectors:
```typescript
const queryEmbedding = [
/* your query vector */
]
const documentIds = ["doc-id-1", "doc-id-2"]
const results = await connection.query(
`
DECLARE @question AS VECTOR (1998) = @0;
SELECT TOP (10) dc.*,
VECTOR_DISTANCE('cosine', @question, embedding) AS distance,
d.fileName as "documentName"
FROM document_chunks dc
LEFT JOIN documents d ON dc.documentId = d.id
WHERE documentId IN (@1))
ORDER BY VECTOR_DISTANCE('cosine', @question, embedding)
`,
[JSON.stringify(queryEmbedding), documentIds.join(", ")],
)
```
## Distance Metrics
The `VECTOR_DISTANCE` function supports different distance metrics:
- `'cosine'` - Cosine distance (most common for semantic search)
- `'euclidean'` - Euclidean (L2) distance
- `'dot'` - Negative dot product
## Requirements
- SQL Server with vector support enabled
- TypeORM with SQL Server driver (`mssql` package)
## Running the Sample
1. Make sure you have SQL Server running with vector support
2. Update the connection settings in `app.ts` if needed
3. Run:
```bash
npm install
ts-node app.ts
```
## Notes
- Vector dimensions must be specified using the `length` option
- Embeddings are stored as JSON strings internally and converted to/from arrays automatically
- The maximum vector dimension depends on your SQL Server version and configuration

View File

@ -0,0 +1,88 @@
import "reflect-metadata"
import { DataSource } from "../../src/index"
import { DocumentChunk } from "./entity/DocumentChunk"
import { Document } from "./entity/Document"
const AppDataSource = new DataSource({
type: "mssql",
host: "localhost",
username: "sa",
password: "Admin12345",
database: "test",
synchronize: true,
dropSchema: true,
logging: false,
entities: [Document, DocumentChunk],
options: {
// Enable trust server certificate for local development
trustServerCertificate: true,
},
})
AppDataSource.initialize()
.then(async (connection) => {
console.log("Inserting documents and chunks with vector embeddings...")
// Create a document
const document = new Document()
document.fileName = "sample-document.txt"
await connection.manager.save(document)
// Generate sample embeddings (in a real app, these would come from an ML model)
const generateEmbedding = (dimension: number): number[] => {
return Array.from({ length: dimension }, () => Math.random())
}
// Create document chunks with embeddings
const chunk1 = new DocumentChunk()
chunk1.content =
"TypeORM is an ORM that can run in NodeJS and can be used with TypeScript and JavaScript."
chunk1.embedding = generateEmbedding(1998)
chunk1.document = document
const chunk2 = new DocumentChunk()
chunk2.content =
"It supports both Active Record and Data Mapper patterns."
chunk2.embedding = generateEmbedding(1998)
chunk2.document = document
const chunk3 = new DocumentChunk()
chunk3.content =
"TypeORM supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, and more."
chunk3.embedding = generateEmbedding(1998)
chunk3.document = document
await connection.manager.save([chunk1, chunk2, chunk3])
console.log("Documents and chunks have been saved!")
// Perform a vector similarity search
console.log("\nPerforming vector similarity search...")
// Query embedding (in a real app, this would be generated from user query)
const queryEmbedding = generateEmbedding(1998)
const documentIds = [document.id]
const docIdParams = documentIds.map((_, i) => `@${i + 1}`).join(", ")
const results = await connection.query(
`
DECLARE @question AS VECTOR (1998) = @0;
SELECT TOP (3) dc.*, VECTOR_DISTANCE('cosine', @question, embedding) AS distance, d.fileName as "documentName"
FROM document_chunks dc
LEFT JOIN documents d ON dc.documentId = d.id
WHERE documentId IN (${docIdParams})
ORDER BY VECTOR_DISTANCE('cosine', @question, embedding)
`,
[JSON.stringify(queryEmbedding), ...documentIds],
)
console.log("Search results (top 3 most similar chunks):")
results.forEach((result: any, index: number) => {
console.log(`\n${index + 1}. Distance: ${result.distance}`)
console.log(` Content: ${result.content.substring(0, 80)}...`)
console.log(` Document: ${result.documentName}`)
})
await connection.destroy()
})
.catch((error) => console.log(error))

View File

@ -0,0 +1,19 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
OneToMany,
} from "../../../src/index"
import { DocumentChunk } from "./DocumentChunk"
@Entity("documents")
export class Document {
@PrimaryGeneratedColumn("uuid")
id: string
@Column("varchar")
fileName: string
@OneToMany(() => DocumentChunk, (chunk) => chunk.document)
chunks: DocumentChunk[]
}

View File

@ -0,0 +1,27 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
ManyToOne,
JoinColumn,
} from "../../../src/index"
import { Document } from "./Document"
@Entity("document_chunks")
export class DocumentChunk {
@PrimaryGeneratedColumn("uuid")
id: string
@Column("varchar", { length: "MAX" })
content: string
@Column("vector", { length: 1998 })
embedding: number[]
@Column("uuid")
documentId: string
@ManyToOne(() => Document, (document) => document.chunks)
@JoinColumn({ name: "documentId" })
document: Document
}

View File

@ -0,0 +1,19 @@
{
"name": "sample37-vector-sqlserver",
"version": "0.0.1",
"description": "Sample demonstrating vector type support in SQL Server with TypeORM",
"main": "app.ts",
"scripts": {
"start": "ts-node app.ts"
},
"dependencies": {
"typeorm": "latest",
"mssql": "^11.0.0",
"reflect-metadata": "^0.2.2"
},
"devDependencies": {
"@types/node": "^22.0.0",
"ts-node": "^10.9.2",
"typescript": "^5.5.4"
}
}

View File

@ -619,6 +619,7 @@ AppDataSource.initialize().then(async () => {
environment:
SA_PASSWORD: "Admin12345"
ACCEPT_EULA: "Y"
MSSQL_PID: "Express"
`
case "mongodb":

View File

@ -49,6 +49,7 @@ export class MssqlParameter {
constructor(value: any, type: "geography")
constructor(value: any, type: "geometry")
constructor(value: any, type: "rowversion")
constructor(value: any, type: "vector", length: number)
constructor(public value: any, public type: string, ...params: number[]) {
this.params = params || []
}

View File

@ -142,6 +142,7 @@ export class SqlServerDriver implements Driver {
"geometry",
"geography",
"rowversion",
"vector",
]
/**
@ -164,6 +165,7 @@ export class SqlServerDriver implements Driver {
"nvarchar",
"binary",
"varbinary",
"vector",
]
/**
@ -233,6 +235,7 @@ export class SqlServerDriver implements Driver {
time: { precision: 7 },
datetime2: { precision: 7 },
datetimeoffset: { precision: 7 },
vector: { length: 255 }, // default length if not provided a value
}
cteCapabilities: CteCapabilities = {
@ -549,6 +552,12 @@ export class SqlServerDriver implements Driver {
return DateUtils.simpleJsonToString(value)
} else if (columnMetadata.type === "simple-enum") {
return DateUtils.simpleEnumToString(value)
} else if (columnMetadata.type === "vector") {
if (Array.isArray(value)) {
return JSON.stringify(value)
} else {
return value
}
}
return value
@ -586,6 +595,14 @@ export class SqlServerDriver implements Driver {
value = DateUtils.stringToSimpleJson(value)
} else if (columnMetadata.type === "simple-enum") {
value = DateUtils.stringToSimpleEnum(value, columnMetadata)
} else if (columnMetadata.type === "vector") {
if (typeof value === "string") {
try {
value = JSON.parse(value)
} catch (e) {
// If parsing fails, return the value as-is
}
}
} else if (columnMetadata.type === Number) {
// convert to number if number
value = !isNaN(+value) ? parseInt(value) : value
@ -707,8 +724,12 @@ export class SqlServerDriver implements Driver {
let type = column.type
// Handle vector type with length (dimensions)
if (column.type === "vector") {
type = `vector(${column.length})`
}
// used 'getColumnLength()' method, because SqlServer sets `varchar` and `nvarchar` length to 1 by default.
if (this.getColumnLength(column)) {
else if (this.getColumnLength(column)) {
type += `(${this.getColumnLength(column)})`
} else if (
column.precision !== null &&

View File

@ -3151,14 +3151,24 @@ export class SqlServerQueryRunner
if (length === "-1") {
tableColumn.length = "MAX"
} else {
tableColumn.length =
!this.isDefaultColumnLength(
table,
tableColumn,
length,
)
? length
: ""
if (tableColumn.type === "vector") {
const len = +length
// NOTE: real returned length is (N*4 + 8) where N is desired dimensions
if (!Number.isNaN(len)) {
tableColumn.length = String(
(len - 8) / 4,
)
}
} else {
tableColumn.length =
!this.isDefaultColumnLength(
table,
tableColumn,
length,
)
? length
: ""
}
}
}
@ -4134,6 +4144,8 @@ export class SqlServerQueryRunner
return this.driver.mssql.UDT
case "rowversion":
return this.driver.mssql.RowVersion
case "vector":
return this.driver.mssql.Ntext
}
}

View File

@ -75,7 +75,7 @@ export type WithLengthColumnType =
| "binary" // mssql
| "varbinary" // mssql, sap
| "string" // cockroachdb, spanner
| "vector" // postgres, sap
| "vector" // postgres, mssql, sap
| "halfvec" // postgres, sap
| "half_vector" // sap
| "real_vector" // sap

View File

@ -0,0 +1,18 @@
import { Entity } from "../../../../../../src/decorator/entity/Entity"
import { Column } from "../../../../../../src/decorator/columns/Column"
import { PrimaryGeneratedColumn } from "../../../../../../src/decorator/columns/PrimaryGeneratedColumn"
@Entity()
export class DocumentChunk {
@PrimaryGeneratedColumn()
id: number
@Column("varchar", { nullable: true })
content: string
@Column("vector", { length: 1998, nullable: true })
embedding: number[]
@Column("varchar", { nullable: true })
documentId: string
}

View File

@ -0,0 +1,15 @@
import { Entity } from "../../../../../../src/decorator/entity/Entity"
import { Column } from "../../../../../../src/decorator/columns/Column"
import { PrimaryGeneratedColumn } from "../../../../../../src/decorator/columns/PrimaryGeneratedColumn"
@Entity()
export class Point {
@PrimaryGeneratedColumn()
id: number
@Column("varchar", { nullable: true })
name: string
@Column("vector", { length: 3, nullable: true })
coords: number[]
}

View File

@ -0,0 +1,222 @@
import "reflect-metadata"
import { expect } from "chai"
import { DataSource } from "../../../../../src/data-source/DataSource"
import {
closeTestingConnections,
createTestingConnections,
reloadTestingDatabases,
} from "../../../../utils/test-utils"
import { DocumentChunk } from "./entity/DocumentChunk"
import { Point } from "./entity/Point"
describe("columns > vector type > sqlserver", () => {
let connections: DataSource[]
before(async () => {
connections = await createTestingConnections({
entities: [DocumentChunk, Point],
enabledDrivers: ["mssql"],
schemaCreate: true,
dropSchema: true,
})
})
beforeEach(() => reloadTestingDatabases(connections))
after(() => closeTestingConnections(connections))
it("should create vector column with specified dimensions", () =>
Promise.all(
connections.map(async (connection) => {
const queryRunner = connection.createQueryRunner()
const table = await queryRunner.getTable("document_chunk")
await queryRunner.release()
const embeddingColumn = table!.findColumnByName("embedding")
expect(embeddingColumn).to.exist
expect(embeddingColumn!.type).to.equal("vector")
expect(embeddingColumn!.length).to.equal("1998")
}),
))
it("should persist and hydrate vector values", () =>
Promise.all(
connections.map(async (connection) => {
const repository = connection.getRepository(DocumentChunk)
const embedding = Array.from({ length: 1998 }, () =>
Math.random(),
)
const chunk = new DocumentChunk()
chunk.content = "Test content"
chunk.embedding = embedding
await repository.save(chunk)
const loadedChunk = await repository.findOne({
where: { id: chunk.id },
})
expect(loadedChunk).to.exist
expect(loadedChunk!.embedding).to.be.an("array")
expect(loadedChunk!.embedding).to.have.lengthOf(1998)
// Check that values are close (floating point comparison)
loadedChunk!.embedding.forEach((val, idx) => {
expect(val).to.be.closeTo(embedding[idx], 0.0001)
})
}),
))
it("should update vector values", () =>
Promise.all(
connections.map(async (connection) => {
const repository = connection.getRepository(Point)
const point = new Point()
point.name = "Test Point"
point.coords = [1.0, 2.0, 3.0]
await repository.save(point)
point.coords = [4.0, 5.0, 6.0]
await repository.save(point)
const loadedPoint = await repository.findOne({
where: { id: point.id },
})
expect(loadedPoint).to.exist
expect(loadedPoint!.coords).to.deep.equal([4.0, 5.0, 6.0])
}),
))
it("should perform cosine similarity search using VECTOR_DISTANCE", () =>
Promise.all(
connections.map(async (connection) => {
const repository = connection.getRepository(DocumentChunk)
const baseEmbedding = Array.from({ length: 1998 }, () =>
Math.random(),
)
// Create test data with known vectors
const embeddings = [
{
content: "Similar chunk",
embedding: [1.0, 1.0, 1.0, ...baseEmbedding.slice(3)],
},
{
content: "Also similar",
embedding: [1.0, 1.0, 1.1, ...baseEmbedding.slice(3)],
},
{
content: "Very different",
embedding: [
10.0,
10.0,
10.0,
...baseEmbedding.slice(3),
],
},
]
await repository.save(embeddings)
const query = [1.0, 1.0, 1.05, ...baseEmbedding.slice(3)]
const results = await connection.query(
`
DECLARE @query AS VECTOR (1998) = '${JSON.stringify(
query,
)}';
SELECT TOP (2) *, VECTOR_DISTANCE('cosine', @query, embedding) AS distance
FROM document_chunk
ORDER BY VECTOR_DISTANCE('cosine', @query, embedding)
`,
)
expect(results.length).to.equal(2)
// The first two results should be the similar ones
expect(results[0].content).to.be.oneOf([
"Similar chunk",
"Also similar",
])
expect(results[1].content).to.be.oneOf([
"Similar chunk",
"Also similar",
])
// Distance should be small for similar vectors
expect(results[0].distance).to.be.lessThan(0.1)
}),
))
it("should perform euclidean distance search using VECTOR_DISTANCE", () =>
Promise.all(
connections.map(async (connection) => {
const repository = connection.getRepository(Point)
// Create test data with known vectors
const points = [
{
name: "Nearest point",
coords: [1.0, 1.0, 1.0],
},
{
name: "Also near",
coords: [1.0, 1.0, 1.1],
},
{
name: "Very different",
coords: [10.0, 10.0, 10.0],
},
]
await repository.save(points)
const origin = [1.0, 1.0, 1.05]
const results = await connection.query(
`
DECLARE @origin AS VECTOR (3) = '${JSON.stringify(origin)}';
SELECT TOP (2) *, VECTOR_DISTANCE('euclidean', @origin, coords) AS distance
FROM point
ORDER BY VECTOR_DISTANCE('euclidean', @origin, coords)
`,
)
expect(results.length).to.equal(2)
// The first two results should be the similar ones
expect(results[0].name).to.be.oneOf([
"Nearest point",
"Also near",
])
expect(results[1].name).to.be.oneOf([
"Nearest point",
"Also near",
])
// Distance should be small for similar vectors
expect(results[0].distance).to.be.lessThan(0.1)
}),
))
it("should handle null vector values", () =>
Promise.all(
connections.map(async (connection) => {
const repository = connection.getRepository(DocumentChunk)
const chunk = new DocumentChunk()
chunk.content = "No embedding"
chunk.embedding = null as any
chunk.documentId = "doc-789"
await repository.save(chunk)
const loadedChunk = await repository.findOne({
where: { id: chunk.id },
})
expect(loadedChunk).to.exist
expect(loadedChunk!.embedding).to.be.null
}),
))
})