Implement LmxOpcUa server — all 6 phases complete

Full OPC UA server on .NET Framework 4.8 (x86) exposing AVEVA System
Platform Galaxy tags via MXAccess. Mirrors Galaxy object hierarchy as
OPC UA address space, translating contained-name browse paths to
tag-name runtime references.

Components implemented:
- Configuration: AppConfiguration with 4 sections, validator
- Domain: ConnectionState, Quality, Vtq, MxDataTypeMapper, error codes
- MxAccess: StaComThread, MxAccessClient (partial classes), MxProxyAdapter
  using strongly-typed ArchestrA.MxAccess COM interop
- Galaxy Repository: SQL queries (hierarchy, attributes, change detection),
  ChangeDetectionService with auto-rebuild on deploy
- OPC UA Server: LmxNodeManager (CustomNodeManager2), LmxOpcUaServer,
  OpcUaServerHost with programmatic config, SecurityPolicy None
- Status Dashboard: HTTP server with HTML/JSON/health endpoints
- Integration: Full 14-step startup, graceful shutdown, component wiring

175 tests (174 unit + 1 integration), all passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-03-25 05:55:27 -04:00
commit a7576ffb38
283 changed files with 16493 additions and 0 deletions

29
.gitignore vendored Normal file
View File

@@ -0,0 +1,29 @@
# Build outputs
bin/
obj/
publish/
# IDE
.vs/
.idea/
*.user
*.suo
# Logs
logs/
# OS
Thumbs.db
desktop.ini
.DS_Store
# NuGet
packages/
*.nupkg
# Certificates
*.pfx
*.pem
# OPC sample server (large external repo)
tools/opcsampleserver/

113
CLAUDE.md Normal file
View File

@@ -0,0 +1,113 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Goal
Build an OPC UA server on .NET Framework 4.8 (32-bit) that exposes AVEVA System Platform (Wonderware) Galaxy tags via the MXAccess toolkit. The server mirrors the Galaxy object hierarchy as an OPC UA address space, translating between contained-name browse paths and tag-name runtime references.
## Architecture Overview
### Data Flow
1. **Galaxy Repository DB (ZB)** — SQL Server database holding the deployed object hierarchy and attribute definitions. Queried at startup and on change detection to build/rebuild the OPC UA address space.
2. **MXAccess COM API** — Runtime data access layer. Subscribes to Galaxy tag attributes for live read/write. Requires a dedicated STA thread with a Win32 message pump for COM callbacks.
3. **OPC UA Server** — Exposes the hierarchy as browse nodes and attributes as variable nodes. Clients browse via contained names but reads/writes are translated to `tag_name.AttributeName` format for MXAccess.
### Key Concept: Contained Name vs Tag Name
Galaxy objects have two names:
- **contained_name** — human-readable name scoped to parent (used for OPC UA browse tree)
- **tag_name** — globally unique system name (used for MXAccess read/write)
Example: browsing `TestMachine_001/DelmiaReceiver/DownloadPath` translates to MXAccess reference `DelmiaReceiver_001.DownloadPath`.
See `gr/layout.md` for the full mapping and target OPC UA structure.
### Data Type Mapping
Galaxy `mx_data_type` values map to OPC UA types (Boolean, Int32, Float, Double, String, DateTime, etc.). Array attributes use ValueRank=1 with ArrayDimensions from the Galaxy attribute definition. Full mapping in `gr/data_type_mapping.md`.
### Change Detection
Poll `galaxy.time_of_last_deploy` in the ZB database to detect redeployments, then rebuild the address space. See `gr/build_layout_plan.md` for the step-by-step plan.
## Reference Implementation
An existing MXAccess client implementation is at:
`C:\Users\dohertj2\Desktop\scadalink-design\lmxproxy\src\ZB.MOM.WW.LmxProxy.Host`
Key patterns from that codebase:
- **StaComThread** — Dedicated STA thread with Win32 message pump (`GetMessage`/`DispatchMessage` loop). All MXAccess COM objects must be created and called on this thread. Uses `PostThreadMessage(WM_APP)` to marshal work items.
- **LMXProxyServer COM object** — `Register(clientName)` returns a connection handle. `AddItem(handle, address)` + `AdviseSupervisory(handle, itemHandle)` for subscriptions. `OnDataChange`/`OnWriteComplete` events for callbacks.
- **Reconnect** — Stored subscriptions are replayed after reconnect. A probe tag subscription monitors connection health.
- **COM cleanup** — `Marshal.ReleaseComObject()` on disconnect. Event handlers must be unwired before unregister.
## MXAccess Documentation
`mxaccess_documentation.md` in the project root contains the full ArchestrA MXAccess Toolkit User's Guide. Key API: `ArchestrA.MxAccess` namespace, `LMXProxyServer` class. The toolkit DLLs are in `Program Files (x86)\ArchestrA\Framework\bin`.
## Galaxy Repository Database
Connection: `sqlcmd -S localhost -d ZB -E` (Windows Auth). See `gr/connectioninfo.md`.
The `gr/` folder contains:
- `queries/` — SQL for hierarchy extraction, attribute lookup, and change detection
- `ddl/tables/` and `ddl/views/` — Schema definitions
- `schema.md` — Full table/view reference
- `build_layout_plan.md` — Step-by-step plan for building the OPC UA address space from DB queries
- `gr/CLAUDE.md` — Detailed guidance for working within the `gr/` subfolder
Key tables: `gobject` (hierarchy/deployment), `template_definition` (object categories), `dynamic_attribute` (user-defined attributes), `primitive_instance` (primitive-to-attribute links), `galaxy` (change detection).
## Build Commands
```bash
dotnet restore ZB.MOM.WW.LmxOpcUa.slnx
dotnet build ZB.MOM.WW.LmxOpcUa.slnx
dotnet test ZB.MOM.WW.LmxOpcUa.slnx # all tests
dotnet test tests/ZB.MOM.WW.LmxOpcUa.Tests # unit tests only
dotnet test tests/ZB.MOM.WW.LmxOpcUa.IntegrationTests # integration tests only
dotnet test --filter "FullyQualifiedName~MyTestClass.MyMethod" # single test
```
## Build & Runtime Constraints
- Language: C#, .NET Framework 4.8, **x86 (32-bit)** platform target — required for MXAccess COM interop
- MXAccess requires a deployed ArchestrA Platform on the machine running the server
- COM apartment: MXAccess objects must live on an STA thread with a message pump
## Library Preferences
- **Logging**: Serilog with rolling daily file sink
- **Unit tests**: xUnit + Shouldly for assertions
- **Service hosting**: TopShelf (Windows service install/uninstall/run as console)
- **OPC UA**: OPC Foundation UA .NET Standard stack (https://github.com/opcfoundation/ua-.netstandard) — NuGet: `OPCFoundation.NetStandard.Opc.Ua.Server`
## OPC UA .NET Standard Documentation
Use the DeepWiki MCP (`mcp__deepwiki`) to query documentation for the OPC UA .NET Standard stack: `https://deepwiki.com/OPCFoundation/UA-.NETStandard`. Tools: `read_wiki_structure`, `read_wiki_contents`, and `ask_question` with repo `OPCFoundation/UA-.NETStandard`.
## Testing
Use the dotnet OPC UA CLI tool at `tools/opcuacli-dotnet/` for manual testing against the running OPC UA server. Supports connect, read, write, subscribe, and browse commands. See `tools/opcuacli-dotnet/README.md` for usage details.
```bash
cd tools/opcuacli-dotnet
dotnet run -- connect -u opc.tcp://localhost:4840
dotnet run -- browse -u opc.tcp://localhost:4840 -r -d 3
dotnet run -- read -u opc.tcp://localhost:4840 -n "ns=2;s=SomeNode"
dotnet run -- subscribe -u opc.tcp://localhost:4840 -n "ns=2;s=SomeNode" -i 500
```
### OPC PLC Sample Server
A test OPC UA server is available at `tools/opcsampleserver/` (Azure IoT OPC PLC). It generates simulated data nodes (slow, fast, anomaly, GUID) on `opc.tcp://localhost:50000`. Must run from the `publish` directory or use the provided batch scripts. Requires `--unsecuretransport` flag for the CLI tool to connect. See `tools/opcsampleserver/README.md` for full details.
```bash
# Start the test server
cd tools/opcsampleserver/publish && dotnet opcplc.dll --pn=50000 --autoaccept --unsecuretransport --sn=5 --sr=10 --st=uint --fn=5 --fr=1 --ft=uint
# Or use the batch script
tools/opcsampleserver/start-server.bat
```

9
ZB.MOM.WW.LmxOpcUa.slnx Normal file
View File

@@ -0,0 +1,9 @@
<Solution>
<Folder Name="/src/">
<Project Path="src/ZB.MOM.WW.LmxOpcUa.Host/ZB.MOM.WW.LmxOpcUa.Host.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/ZB.MOM.WW.LmxOpcUa.Tests/ZB.MOM.WW.LmxOpcUa.Tests.csproj" />
<Project Path="tests/ZB.MOM.WW.LmxOpcUa.IntegrationTests/ZB.MOM.WW.LmxOpcUa.IntegrationTests.csproj" />
</Folder>
</Solution>

BIN
dashboard.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

384
docs/implementation-plan.md Normal file
View File

@@ -0,0 +1,384 @@
# Implementation Plan: LmxOpcUa Server — All 44 Requirements
## Context
The LmxOpcUa project is scaffolded (solution, projects, configs, requirements docs) but has no implementation beyond Program.cs and a stub OpcUaService.cs. This plan implements all 44 requirements across 6 phases, each with verification gates and wiring checks to ensure nothing is left unconnected.
## Architecture
Five major components wired together in OpcUaService.cs:
```
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Galaxy Repository│────>│ OPC UA Server │<───>│ OPC UA Clients │
│ (SQL queries) │ │ (address space) │ │ │
└─────────────────┘ └────────┬──────────┘ └─────────────────┘
┌────────┴──────────┐
│ MxAccessClient │
│ (STA + COM) │
└───────────────────┘
┌────────┴──────────┐
│ Status Dashboard │
│ (HTTP + metrics) │
└───────────────────┘
```
Reference implementation: `C:\Users\dohertj2\Desktop\scadalink-design\lmxproxy\src\ZB.MOM.WW.LmxProxy.Host\`
---
## PHASE 1: Foundation — Domain Models, Configuration, Interfaces
**Reqs:** SVC-003, SVC-006 (partial), MXA-008 (interfaces), MXA-009, OPC-005, OPC-012 (partial), GR-005 (config)
### Files to Create
**Configuration/**
- `AppConfiguration.cs` — top-level holder for all config sections
- `OpcUaConfiguration.cs` — Port, EndpointPath, ServerName, GalaxyName, MaxSessions, SessionTimeoutMinutes
- `MxAccessConfiguration.cs` — ClientName, timeouts, concurrency, probe settings
- `GalaxyRepositoryConfiguration.cs` — ConnectionString, intervals, command timeout
- `DashboardConfiguration.cs` — Enabled, Port, RefreshIntervalSeconds
- `ConfigurationValidator.cs` — validate and log effective config at startup
**Domain/**
- `ConnectionState.cs` — enum: Disconnected, Connecting, Connected, Disconnecting, Error, Reconnecting
- `ConnectionStateChangedEventArgs.cs` — PreviousState, CurrentState, Message
- `Vtq.cs` — Value/Timestamp/Quality struct with factory methods
- `Quality.cs` — enum with Bad/Uncertain/Good families matching OPC DA codes
- `QualityMapper.cs` — MapFromMxAccessQuality(int) and MapToOpcUaStatusCode(Quality)
- `MxDataTypeMapper.cs` — MapToOpcUaDataType(int mxDataType), MapToClrType(int). Unknown defaults to String
- `MxErrorCodes.cs` — translate 1008/1012/1013 to human messages
- `GalaxyObjectInfo.cs` — DTO matching hierarchy.sql columns
- `GalaxyAttributeInfo.cs` — DTO matching attributes.sql columns
- `IMxAccessClient.cs` — interface: Connect, Disconnect, Subscribe, Read, Write, OnTagValueChanged delegate
- `IGalaxyRepository.cs` — interface: GetHierarchy, GetAttributes, GetLastDeployTime, TestConnection, OnGalaxyChanged event
- `IMxProxy.cs` — abstraction over LMXProxyServer COM object (enables testing without DLL)
**Metrics/**
- `PerformanceMetrics.cs` — ITimingScope, OperationMetrics (1000-entry rolling buffer), BeginOperation/GetStatistics. Adapt from reference.
### Tests
- `ConfigurationLoadingTests.cs` — bind appsettings.json, verify defaults
- `MxDataTypeMapperTests.cs` — all 12 type mappings + unknown default
- `QualityMapperTests.cs` — boundary values (0, 63, 64, 191, 192)
- `MxErrorCodesTests.cs` — known codes + unknown
- `PerformanceMetricsTests.cs` — recording, P95, buffer eviction, empty state
### Verification Gate 1
- [ ] `dotnet build` — zero errors
- [ ] All Phase 1 tests pass
- [ ] Config binding loads all 4 sections from appsettings.json
- [ ] MxDataTypeMapper covers every row in `gr/data_type_mapping.md`
- [ ] Quality enum covers all reference impl values
- [ ] Builds WITHOUT ArchestrA.MxAccess.dll (interface-based, no COM refs in Phase 1)
- [ ] Every new file has doc-comment referencing requirement ID(s)
- [ ] IMxAccessClient has every method needed by OPC-007, OPC-008, OPC-009
- [ ] IGalaxyRepository has every method needed by GR-001 through GR-004
---
## PHASE 2: MxAccessClient — STA Thread and COM Interop
**Reqs:** MXA-001, MXA-002, MXA-003, MXA-004, MXA-005, MXA-006, MXA-007, MXA-008 (wiring)
### Files to Create
**MxAccess/**
- `StaComThread.cs` — adapt from reference. STA thread, Win32 message pump, RunAsync(Action)/RunAsync<T>(Func<T>), WM_APP dispatch
- `MxAccessClient.cs` — core partial class implementing IMxAccessClient. Fields: StaComThread, IMxProxy, handle, state, semaphores, maps
- `MxAccessClient.Connection.cs` — ConnectAsync (Register on STA), DisconnectAsync (cleanup per MXA-007), COM cleanup
- `MxAccessClient.Subscription.cs` — SubscribeAsync (AddItem+AdviseSupervisory), UnsubscribeAsync, ReplayStoredSubscriptions
- `MxAccessClient.ReadWrite.cs` — ReadAsync (subscribe-get-first-unsubscribe), WriteAsync (Write+OnWriteComplete), semaphore-limited, timeout, ITimingScope metrics
- `MxAccessClient.EventHandlers.cs` — OnDataChange (resolve handle→address, create Vtq, invoke callback, update probe), OnWriteComplete (complete TCS, translate errors)
- `MxAccessClient.Monitor.cs` — monitor loop (reconnect on disconnect, probe staleness→force reconnect), cancellable
- `MxProxyAdapter.cs` — wraps real LMXProxyServer COM object, forwards calls to IMxProxy interface
**Test Helpers (in Tests project):**
- `FakeMxProxy.cs` — implements IMxProxy, simulates connections/data changes for testing
### Design Decision: IMxProxy Abstraction
Code against `IMxProxy` interface (not `LMXProxyServer` directly). This allows testing without ArchestrA.MxAccess.dll. `MxProxyAdapter` wraps the real COM object at runtime.
### Tests
- `StaComThreadTests.cs` — STA apartment verified, work item execution, dispose
- `MxAccessClientConnectionTests.cs` — state transitions, cleanup order
- `MxAccessClientSubscriptionTests.cs` — subscribe/unsubscribe, stored subscriptions, reconnect replay, OnDataChange→callback
- `MxAccessClientReadWriteTests.cs` — read returns value, read timeout, write completes on callback, write timeout, semaphore limiting
- `MxAccessClientMonitorTests.cs` — reconnect on disconnect, probe staleness
### Verification Gate 2
- [ ] Solution builds without ArchestrA.MxAccess.dll
- [ ] STA thread test proves work items execute on STA apartment
- [ ] Connection lifecycle: Disconnected→Connecting→Connected→Disconnecting→Disconnected
- [ ] Subscription replay: stored subscriptions replayed after simulated reconnect
- [ ] Read/Write: timeout behavior returns error within expected window
- [ ] Metrics: Read/Write record timing in PerformanceMetrics
- [ ] **WIRING CHECK:** OnDataChange callback reaches OnTagValueChanged delegate
- [ ] COM cleanup order: UnAdvise→RemoveItem→unwire events→Unregister→ReleaseComObject
- [ ] Error codes 1008/1012/1013 translate correctly in OnWriteComplete path
---
## PHASE 3: Galaxy Repository — SQL Queries and Change Detection
**Reqs:** GR-001, GR-002, GR-003, GR-004, GR-006, GR-007
### Files to Create
**GalaxyRepository/**
- `GalaxyRepositoryService.cs` — implements IGalaxyRepository. SQL embedded as `const string` (from gr/queries/). ADO.NET SqlConnection per-query. GetHierarchyAsync, GetAttributesAsync, GetLastDeployTimeAsync, TestConnectionAsync
- `ChangeDetectionService.cs` — background Timer at configured interval. Polls GetLastDeployTimeAsync, compares to last known, fires OnGalaxyChanged on change. First poll always triggers. Failed poll logs Warning, retries next interval
- `GalaxyRepositoryStats.cs` — POCO for dashboard: GalaxyName, DbConnected, LastDeployTime, ObjectCount, AttributeCount, LastRebuildTime
### Tests
- `ChangeDetectionServiceTests.cs` — first poll triggers, same timestamp skips, changed triggers, failed poll retries
- `GalaxyRepositoryServiceTests.cs` (integration, in IntegrationTests) — TestConnection, GetHierarchy returns rows, GetAttributes returns rows
### Verification Gate 3
- [ ] All SQL is `const string` — no concatenation, no parameters, no INSERT/UPDATE/DELETE (GR-006 code review)
- [ ] GetHierarchyAsync maps all columns: gobject_id, tag_name, contained_name, browse_name, parent_gobject_id, is_area
- [ ] GetAttributesAsync maps all columns including array_dimension
- [ ] Change detection: first poll fires, same timestamp skips, changed fires
- [ ] Failed query does NOT crash or trigger false rebuild
- [ ] GalaxyRepositoryStats populated for dashboard
- [ ] Zero rows from hierarchy logs Warning
---
## PHASE 4: OPC UA Server — Address Space and Node Manager
**Reqs:** OPC-001, OPC-002, OPC-003, OPC-004, OPC-005, OPC-006, OPC-007, OPC-008, OPC-009, OPC-010, OPC-011, OPC-012, OPC-013
### Files to Create
**OpcUa/**
- `LmxOpcUaServer.cs` — inherits StandardServer. Creates custom node manager. SecurityPolicy None. Registers namespace `urn:{GalaxyName}:LmxOpcUa`
- `LmxNodeManager.cs` — inherits CustomNodeManager2. Core class:
- `BuildAddressSpace(hierarchy, attributes)` — creates folder/object/variable nodes from Galaxy data. NodeId: `ns=1;s={tag_name}` / `ns=1;s={tag_name}.{attr}`. Stores full_tag_reference lookup
- `RebuildAddressSpace(hierarchy, attributes)` — removes old nodes, rebuilds. Preserves sessions
- Read/Write overrides delegate to IMxAccessClient via stored full_tag_reference
- Subscription management: ref-counted shared MXAccess subscriptions
- `OpcUaServerHost.cs` — manages ApplicationInstance lifecycle. Programmatic config (no XML). Start/Stop. Exposes ActiveSessionCount
- `OpcUaQualityMapper.cs` — domain Quality → OPC UA StatusCodes
- `DataValueConverter.cs` — COM variant ↔ OPC UA DataValue. Handles all types from data_type_mapping.md. DateTime UTC. Arrays
### Tests
- `DataValueConverterTests.cs` — all type conversions, arrays, DateTime UTC
- `LmxNodeManagerBuildTests.cs` — synthetic hierarchy matching gr/layout.md, verify node types, NodeIds, data types, ValueRank, ArrayDimensions
- `LmxNodeManagerRebuildTests.cs` — rebuild replaces nodes, old nodes gone, new nodes present
- `OpcUaQualityMapperTests.cs` — all quality families
### Verification Gate 4
- [ ] Endpoint URL: `opc.tcp://{hostname}:{port}/LmxOpcUa`
- [ ] Namespace: `urn:{GalaxyName}:LmxOpcUa` at index 1
- [ ] Root ZB folder under Objects
- [ ] Areas → FolderType + Organizes reference
- [ ] Non-areas → BaseObjectType + HasComponent reference
- [ ] Variable nodes: correct DataType, ValueRank, ArrayDimensions per data_type_mapping.md
- [ ] **WIRING CHECK:** Read handler resolves NodeId → full_tag_reference → calls IMxAccessClient.ReadAsync
- [ ] **WIRING CHECK:** Write handler resolves NodeId → full_tag_reference → calls IMxAccessClient.WriteAsync
- [ ] Rebuild removes old nodes, creates new ones without crash
- [ ] SecurityPolicy is None
- [ ] MaxSessions/SessionTimeout configured from appsettings
---
## PHASE 5: Status Dashboard — HTTP, HTML, JSON, Health
**Reqs:** DASH-001 through DASH-009
### Files to Create
**Status/**
- `StatusData.cs` — DTO: ConnectionInfo, HealthInfo, SubscriptionInfo, GalaxyInfo, OperationMetrics, Footer
- `HealthCheckService.cs` — rules: not connected→Unhealthy, success rate<50% w/>100 ops→Degraded, else Healthy
- `StatusReportService.cs` — aggregates from all components. GenerateHtml (self-contained, inline CSS, color-coded panels, meta-refresh). GenerateJson. IsHealthy
- `StatusWebServer.cs` — HttpListener. Routes: / → HTML, /api/status → JSON, /api/health → 200/503. GET only. no-cache headers. Disableable
### Tests
- `HealthCheckServiceTests.cs` — three health rules, messages
- `StatusReportServiceTests.cs` — HTML contains all panels, JSON deserializes, meta-refresh tag
- `StatusWebServerTests.cs` — routing (200/405/404), cache headers, start/stop
### Verification Gate 5
- [ ] HTML contains all panels: Connection, Health, Subscriptions, Galaxy Info, Operations table, Footer
- [ ] Connection panel: green/red/yellow border per state
- [ ] Health panel: three states with correct colors
- [ ] Operations table: Read/Write/Subscribe/Browse with Count/SuccessRate/Avg/Min/Max/P95
- [ ] Galaxy Info panel: galaxy name, DB status, last deploy, object/attribute counts, last rebuild
- [ ] Footer: timestamp + assembly version
- [ ] JSON API: all same data as HTML
- [ ] /api/health: 200 when healthy, 503 when unhealthy
- [ ] Meta-refresh tag with configured interval
- [ ] Port conflict does not prevent service startup
- [ ] Dashboard disabled via config skips HttpListener
---
## PHASE 6: Integration Wiring and End-to-End Verification
**Reqs:** SVC-004, SVC-005, SVC-006, ALL wiring verification
### OpcUaService.cs — Full Implementation
**Start() sequence (SVC-005):**
1. Load AppConfiguration via IConfiguration
2. ConfigurationValidator.ValidateAndLog()
3. Register AppDomain.UnhandledException handler (SVC-006)
4. Create PerformanceMetrics
5. Create MxAccessClient → ConnectAsync (failure = fatal, don't start)
6. Start MxAccessClient monitor loop
7. Create GalaxyRepositoryService → TestConnectionAsync (failure = warning, continue)
8. Create OpcUaServerHost + LmxNodeManager, inject IMxAccessClient
9. Query initial hierarchy + attributes → BuildAddressSpace
10. Start OPC UA server listener (failure = fatal)
11. Create ChangeDetectionService → **wire OnGalaxyChanged → nodeManager.RebuildAddressSpace**
12. Start change detection polling
13. Create HealthCheckService, StatusReportService, StatusWebServer → Start (failure = warning)
14. Log "LmxOpcUa service started successfully"
**Critical wiring (GUARDRAILS):**
- `_mxAccessClient.OnTagValueChanged` → node manager subscription delivery
- `_changeDetectionService.OnGalaxyChanged``_nodeManager.RebuildAddressSpace`
- `_mxAccessClient.ConnectionStateChanged` → health check updates
- Node manager Read/Write → `_mxAccessClient.ReadAsync/WriteAsync`
- StatusReportService reads from: MxAccessClient, PerformanceMetrics, GalaxyRepositoryStats, OpcUaServerHost
**Stop() sequence (SVC-004, reverse order, 30s max):**
1. Cancel CancellationTokenSource (stops all background loops)
2. Stop change detection
3. Stop OPC UA server
4. Disconnect MXAccess (full COM cleanup)
5. Stop StatusWebServer
6. Dispose PerformanceMetrics
7. Log "Service shutdown complete"
### Wiring Verification Tests (GUARDRAILS)
These tests prove components are connected end-to-end, not just implemented in isolation:
- `Wiring/MxAccessToNodeManagerWiringTest.cs` — simulate OnDataChange on FakeMxProxy → verify data reaches node manager subscription delivery
- `Wiring/ChangeDetectionToRebuildWiringTest.cs` — mock GalaxyRepository returns changed timestamp → verify RebuildAddressSpace called
- `Wiring/OpcUaReadToMxAccessWiringTest.cs` — issue Read via NodeManager → verify FakeMxProxy receives correct full_tag_reference
- `Wiring/OpcUaWriteToMxAccessWiringTest.cs` — issue Write via NodeManager → verify FakeMxProxy receives correct tag + value
- `Wiring/ServiceStartupSequenceTest.cs` — create OpcUaService with fakes, call Start(), verify all components created and wired
- `Wiring/ShutdownCompletesTest.cs` — Start then Stop, verify completes within 30s
- `EndToEnd/FullDataFlowTest.cs`**THE ULTIMATE SMOKE TEST**: full service with fakes, verify: (1) address space built, (2) MXAccess data change → OPC UA variable, (3) read → correct tag ref, (4) write → correct tag+value, (5) dashboard HTML has real data
### Verification Gate 6 (FINAL)
- [ ] Startup: all 14 steps execute in order
- [ ] Shutdown: completes within 30s, all components disposed in reverse order
- [ ] **WIRING:** MXAccess OnDataChange → node manager subscription delivery
- [ ] **WIRING:** Galaxy change → address space rebuild
- [ ] **WIRING:** OPC UA Read → MXAccess ReadAsync with correct tag reference
- [ ] **WIRING:** OPC UA Write → MXAccess WriteAsync with correct tag+value
- [ ] **WIRING:** Dashboard aggregates data from all components
- [ ] **WIRING:** Health endpoint reflects actual connection state
- [ ] AppDomain.UnhandledException registered
- [ ] TopShelf recovery configured (restart, 60s delay)
- [ ] FullDataFlowTest passes end-to-end
---
## Master Requirement Traceability (all 44)
| Req | Phase | Verified By |
|-----|-------|-------------|
| SVC-001 | Done | Program.cs already configured |
| SVC-002 | Done | Program.cs already configured |
| SVC-003 | 1 | ConfigurationLoadingTests |
| SVC-004 | 6 | ShutdownCompletesTest |
| SVC-005 | 6 | ServiceStartupSequenceTest |
| SVC-006 | 6 | AppDomain handler registration test |
| MXA-001 | 2 | StaComThreadTests |
| MXA-002 | 2 | MxAccessClientConnectionTests |
| MXA-003 | 2 | MxAccessClientSubscriptionTests |
| MXA-004 | 2 | MxAccessClientReadWriteTests |
| MXA-005 | 2 | MxAccessClientMonitorTests |
| MXA-006 | 2 | MxAccessClientMonitorTests (probe) |
| MXA-007 | 2 | Cleanup order test |
| MXA-008 | 2 | Metrics integration in ReadWrite |
| MXA-009 | 1+2 | MxErrorCodesTests + write error path |
| GR-001 | 3 | GetHierarchyAsync maps all columns |
| GR-002 | 3 | GetAttributesAsync maps all columns |
| GR-003 | 3 | ChangeDetectionServiceTests |
| GR-004 | 3+6 | ChangeDetectionToRebuildWiringTest |
| GR-005 | 1+3 | Config tests + ADO.NET usage |
| GR-006 | 3 | Code review: const string SQL only |
| GR-007 | 3 | TestConnectionAsync test |
| OPC-001 | 4 | Endpoint URL test |
| OPC-002 | 4 | BuildTests: node types + references |
| OPC-003 | 4 | BuildTests: variable nodes |
| OPC-004 | 4+6 | ReadWiringTest: browse→tag_name |
| OPC-005 | 1+4 | MxDataTypeMapperTests + variable node DataType |
| OPC-006 | 4 | BuildTests: ValueRank + ArrayDimensions |
| OPC-007 | 4+6 | OpcUaReadToMxAccessWiringTest |
| OPC-008 | 4+6 | OpcUaWriteToMxAccessWiringTest |
| OPC-009 | 4+6 | MxAccessToNodeManagerWiringTest |
| OPC-010 | 4+6 | RebuildTests + ChangeDetectionToRebuildWiringTest |
| OPC-011 | 4 | ServerStatus node test |
| OPC-012 | 4 | Namespace URI test |
| OPC-013 | 4 | Session config test |
| DASH-001 | 5 | StatusWebServerTests routing |
| DASH-002 | 5 | HTML contains Connection panel |
| DASH-003 | 5 | HealthCheckServiceTests |
| DASH-004 | 5 | HTML contains Subscriptions panel |
| DASH-005 | 5 | HTML contains Operations table |
| DASH-006 | 5 | HTML contains Footer |
| DASH-007 | 5 | Meta-refresh tag test |
| DASH-008 | 5 | JSON API deserialization test |
| DASH-009 | 5 | HTML contains Galaxy Info panel |
---
## Final Folder Structure
```
src/ZB.MOM.WW.LmxOpcUa.Host/
Configuration/ (Phase 1)
Domain/ (Phase 1)
Metrics/ (Phase 1)
MxAccess/ (Phase 2)
GalaxyRepository/ (Phase 3)
OpcUa/ (Phase 4)
Status/ (Phase 5)
OpcUaService.cs (Phase 6 — full wiring)
Program.cs (existing)
appsettings.json (existing)
tests/ZB.MOM.WW.LmxOpcUa.Tests/
Configuration/ (Phase 1)
Domain/ (Phase 1)
Metrics/ (Phase 1)
MxAccess/ (Phase 2)
GalaxyRepository/ (Phase 3)
OpcUa/ (Phase 4)
Status/ (Phase 5)
Wiring/ (Phase 6 — GUARDRAILS)
EndToEnd/ (Phase 6 — GUARDRAILS)
Helpers/FakeMxProxy.cs (Phase 2)
```
## Verification: How to Run
```bash
# Build
dotnet build ZB.MOM.WW.LmxOpcUa.slnx
# All tests
dotnet test ZB.MOM.WW.LmxOpcUa.slnx
# Phase-specific (by namespace convention)
dotnet test tests/ZB.MOM.WW.LmxOpcUa.Tests --filter "FullyQualifiedName~Configuration"
dotnet test tests/ZB.MOM.WW.LmxOpcUa.Tests --filter "FullyQualifiedName~MxAccess"
dotnet test tests/ZB.MOM.WW.LmxOpcUa.Tests --filter "FullyQualifiedName~GalaxyRepository"
dotnet test tests/ZB.MOM.WW.LmxOpcUa.Tests --filter "FullyQualifiedName~OpcUa"
dotnet test tests/ZB.MOM.WW.LmxOpcUa.Tests --filter "FullyQualifiedName~Status"
dotnet test tests/ZB.MOM.WW.LmxOpcUa.Tests --filter "FullyQualifiedName~Wiring"
dotnet test tests/ZB.MOM.WW.LmxOpcUa.Tests --filter "FullyQualifiedName~EndToEnd"
# Integration tests (requires ZB database)
dotnet test tests/ZB.MOM.WW.LmxOpcUa.IntegrationTests
```

View File

@@ -0,0 +1,121 @@
# Galaxy Repository — Component Requirements
Parent: [HLR-002](HighLevelReqs.md#hlr-002-galaxy-hierarchy-as-opc-ua-address-space), [HLR-005](HighLevelReqs.md#hlr-005-dynamic-address-space-rebuild)
## GR-001: Hierarchy Extraction
The system shall query the Galaxy Repository database to extract all deployed objects with their parent-child containment relationships, contained names, and tag names.
### Acceptance Criteria
- Executes `queries/hierarchy.sql` against the ZB database.
- Returns a list of objects with: `gobject_id`, `tag_name`, `contained_name`, `browse_name`, `parent_gobject_id`, `is_area`.
- Objects with `parent_gobject_id = 0` are children of the root ZB node.
- Only deployed, non-template objects matching the category filter (areas, engines, user-defined objects, etc.) are returned.
- Query completes within 10 seconds on a typical Galaxy (hundreds of objects). Log a Warning if it takes longer.
### Details
- Results are ordered by `parent_gobject_id, tag_name` for deterministic tree building.
- If the query returns zero rows, log a Warning (Galaxy may have no deployed objects, or the DB connection may be misconfigured).
- Orphan detection: if a row references a `parent_gobject_id` that does not exist in the result set and is not 0, log a Warning and skip that node.
---
## GR-002: Attribute Extraction
The system shall query user-defined (dynamic) attributes for deployed objects, including data type, array flag, and array dimensions.
### Acceptance Criteria
- Executes `queries/attributes.sql` using the template chain CTE to resolve inherited attributes.
- Returns: `gobject_id`, `tag_name`, `attribute_name`, `full_tag_reference`, `mx_data_type`, `is_array`, `array_dimension`, `security_classification`.
- Attributes starting with `_` are filtered out by the query.
- `array_dimension` is correctly extracted from the `mx_value` hex bytes (positions 13-16, little-endian uint16).
### Details
- CTE recursion depth is limited to 10 levels (per the query). This is sufficient for Galaxy template hierarchies.
- If `mx_data_type` is null or not in the known set (1-8, 13-16), default to String.
- If `gobject_id` from an attribute row does not match any hierarchy object, skip that attribute (object may not be deployed).
---
## GR-003: Change Detection
The system shall poll `galaxy.time_of_last_deploy` at a configurable interval to detect when a new deployment has occurred.
### Acceptance Criteria
- Polls `SELECT time_of_last_deploy FROM galaxy` at a configurable interval (`GalaxyRepository:ChangeDetectionIntervalSeconds`, default 30 seconds).
- Compares the returned timestamp to the last known value stored in memory.
- If different, triggers a rebuild (re-run hierarchy + attributes queries, notify OPC UA server).
- First poll after startup always triggers an initial build.
- If the query fails (SQL timeout, connection error), log Warning and retry at next interval. Do not trigger a rebuild on failure.
### Details
- Polling runs on a background timer thread, not blocking the STA thread.
- `time_of_last_deploy` is a datetime column. Compare using exact equality (not range).
---
## GR-004: Rebuild on Change
When a deployment change is detected, the system shall re-query hierarchy and attributes and provide the updated structure to the OPC UA server for address space rebuild.
### Acceptance Criteria
- On change detection, re-query both hierarchy and attributes.
- Provide the new data set to the OPC UA server component for address space replacement.
- Log at Information level: "Galaxy deployment change detected. Rebuilding address space. ({ObjectCount} objects, {AttributeCount} attributes)".
- Log total rebuild time at Information level.
- If the re-query fails, log Error and keep the existing address space (do not clear it).
### Details
- Rebuild is not atomic from the DB perspective — hierarchy and attributes are two separate queries. This is acceptable; deployment is an infrequent operation.
- Raise an event/callback that the OPC UA server subscribes to: `OnGalaxyChanged(hierarchyData, attributeData)`.
---
## GR-005: Connection Configuration
Database connection parameters shall be configurable via appsettings.json (connection string using Windows Authentication by default).
### Acceptance Criteria
- Connection string in `appsettings.json` under `GalaxyRepository:ConnectionString`.
- Default: `Server=localhost;Database=ZB;Integrated Security=true` (Windows Auth).
- ADO.NET `SqlConnection` used for queries (.NET Framework 4.8 built-in).
- Connection is opened per-query (not kept open). Connection pooling handles efficiency.
- If the initial connection test at startup fails, log Error with the connection string and continue attempting (change detection polls will keep retrying).
### Details
- Command timeout: configurable via `GalaxyRepository:CommandTimeoutSeconds`, default 30 seconds.
- No ORM. Raw ADO.NET with `SqlCommand` and `SqlDataReader`. SQL text is embedded as constants (not dynamically constructed).
---
## GR-006: Query Safety
All SQL queries shall be static read-only SELECT statements. No writes to the Galaxy Repository database.
### Acceptance Criteria
- All queries are hardcoded SQL strings with no string concatenation or user-supplied parameters.
- No INSERT, UPDATE, DELETE, or DDL statements are ever executed against the Galaxy database.
- Queries use only SELECT with read-only intent.
---
## GR-007: Startup Validation
On startup, the Galaxy Repository component shall validate database connectivity.
### Acceptance Criteria
- Execute a simple test query (`SELECT 1`) against the configured database.
- If the database is unreachable, log an Error but do not prevent service startup.
- The service runs in degraded mode (empty address space) until the database becomes available and the next change detection poll succeeds.

View File

@@ -0,0 +1,47 @@
# High-Level Requirements
## HLR-001: OPC UA Server
The system shall expose an OPC UA server endpoint that OPC UA clients can connect to for browsing, reading, and writing Galaxy tag data.
## HLR-002: Galaxy Hierarchy as OPC UA Address Space
The system shall build an OPC UA address space that mirrors the System Platform Galaxy object hierarchy, using contained names for browse structure and tag names for runtime data access.
## HLR-003: MXAccess Runtime Data Access
The system shall use the MXAccess toolkit to subscribe to, read, and write Galaxy tag attribute values at runtime on behalf of connected OPC UA clients.
## HLR-004: Data Type Mapping
The system shall map Galaxy attribute data types (mx_data_type) to appropriate OPC UA built-in types, including support for array attributes.
## HLR-005: Dynamic Address Space Rebuild
The system shall detect Galaxy deployment changes (via `galaxy.time_of_last_deploy`) and rebuild the OPC UA address space to reflect the current deployed state.
## HLR-006: Windows Service Hosting
The system shall run as a Windows service (via TopShelf) with support for install, uninstall, and interactive console modes.
## HLR-007: Logging
The system shall log operational events to rolling daily log files using Serilog.
## HLR-008: Connection Resilience
The system shall automatically reconnect to MXAccess after connection loss, replaying active subscriptions upon reconnect.
## HLR-009: Status Dashboard
The system shall host an embedded HTTP status dashboard (similar to the LmxProxy dashboard) providing at-a-glance operational visibility including connection state, health, subscription statistics, and operation metrics.
## Component-Level Requirements
Detailed requirements are broken out into the following documents:
- [OPC UA Server Requirements](OpcUaServerReqs.md)
- [MXAccess Client Requirements](MxAccessClientReqs.md)
- [Galaxy Repository Requirements](GalaxyRepositoryReqs.md)
- [Service Host Requirements](ServiceHostReqs.md)
- [Status Dashboard Requirements](StatusDashboardReqs.md)

View File

@@ -0,0 +1,172 @@
# MXAccess Client — Component Requirements
Parent: [HLR-003](HighLevelReqs.md#hlr-003-mxaccess-runtime-data-access), [HLR-008](HighLevelReqs.md#hlr-008-connection-resilience)
## MXA-001: STA Thread with Message Pump
All MXAccess COM objects shall be created and called on a dedicated STA thread running a Win32 message pump to ensure COM callbacks are delivered.
### Acceptance Criteria
- A dedicated thread is created with `ApartmentState.STA` before any MXAccess COM objects are instantiated.
- The thread runs a Win32 message pump using `GetMessage`/`TranslateMessage`/`DispatchMessage` loop.
- Work items are marshalled to the STA thread via `PostThreadMessage(WM_APP)` and a concurrent queue.
- The STA thread processes work items between message pump iterations.
- All COM object creation (`LMXProxyServer` constructor), method calls, and event callbacks happen on this thread.
### Details
- Thread name: `MxAccess-STA` (for diagnostics).
- If the STA thread dies unexpectedly, log Fatal and trigger service shutdown. Do not attempt to create a replacement thread (COM objects on the dead thread are unrecoverable).
- `RunAsync(Action)` method returns a `Task` that completes when the action executes on the STA thread. Callers can `await` it.
---
## MXA-002: Connection Lifecycle
The client shall support Register/Unregister lifecycle with the LMXProxyServer COM object, tracking the connection handle.
### Acceptance Criteria
- `Register(clientName)` is called on the STA thread and returns a positive connection handle on success.
- If Register returns handle <= 0, throw with descriptive error.
- `Unregister(handle)` is called during disconnect after all subscriptions are removed.
- Client name: configurable via `MxAccess:ClientName`, default `LmxOpcUa`. Must be unique per MXAccess registration.
- Connection state transitions: Disconnected → Connecting → Connected → Disconnecting → Disconnected (and Error from any state).
### Details
- `ConnectedSince` timestamp (UTC) is recorded after successful Register.
- `ReconnectCount` is tracked for diagnostics and dashboard display.
- State change events are raised for dashboard and health check consumption.
---
## MXA-003: Tag Subscription
The client shall support subscribing to tags via AddItem + AdviseSupervisory, receiving value updates through OnDataChange callbacks.
### Acceptance Criteria
- Subscribe sequence: `AddItem(handle, address)` returns item handle, then `AdviseSupervisory(handle, itemHandle)` starts the subscription.
- `OnDataChange` callback delivers value, quality (integer), timestamp, and MXSTATUS_PROXY array.
- Item address format: `tag_name.AttributeName` for scalars, `tag_name.AttributeName[]` for whole arrays.
- If AddItem fails (e.g., tag does not exist), log Warning and return failure to caller.
- Bidirectional maps of `address ↔ itemHandle` are maintained for callback resolution.
### Details
- Use `AdviseSupervisory` (not `Advise`) because this is a background service with no interactive user session. AdviseSupervisory allows secured/verified writes without user authentication.
- Stored subscriptions dictionary maps address to callback for reconnect replay.
- On reconnect, all entries in stored subscriptions are re-subscribed (AddItem + AdviseSupervisory with new handles).
---
## MXA-004: Tag Read/Write
The client shall support synchronous-style read and write operations, marshalled to the STA thread, with configurable timeouts.
### Acceptance Criteria
- Read: implemented as subscribe-get-first-value-unsubscribe pattern (AddItem → AdviseSupervisory → wait for OnDataChange → UnAdvise → RemoveItem).
- Write: AddItem → AdviseSupervisory → `Write()` → await `OnWriteComplete` callback → cleanup.
- Read timeout: configurable via `MxAccess:ReadTimeoutSeconds`, default 5 seconds.
- Write timeout: configurable via `MxAccess:WriteTimeoutSeconds`, default 5 seconds. On timeout, log Warning and return timeout error.
- Concurrent operation limit: configurable semaphore via `MxAccess:MaxConcurrentOperations`, default 10.
- All operations marshalled to the STA thread.
### Details
- Write uses security classification -1 (no security). Galaxy runtime handles security enforcement.
- `OnWriteComplete` callback: check MXSTATUS_PROXY `success` field. If 0, extract detail code and propagate error.
- COM exceptions (`COMException` with HRESULT) are caught and translated to meaningful error messages.
---
## MXA-005: Auto-Reconnect
The client shall monitor connection health and automatically reconnect on failure, replaying all stored subscriptions after reconnect.
### Acceptance Criteria
- Monitor loop runs on a background thread, checking connection health at configurable interval (`MxAccess:MonitorIntervalSeconds`, default 5 seconds).
- If disconnected, attempt reconnect. On success, replay all stored subscriptions.
- On reconnect failure, log Warning and retry at next interval (no exponential backoff — reconnect as quickly as possible on a plant-floor service).
- Reconnect count is incremented on each successful reconnect.
- Monitor loop is cancellable (for clean shutdown).
### Details
- Reconnect cleans up old COM objects before creating new ones.
- After reconnect, probe subscription is re-established first, then stored subscriptions.
- No max retry limit — keep trying indefinitely until service is stopped.
---
## MXA-006: Probe-Based Health Monitoring
The client shall optionally subscribe to a configurable probe tag and use OnDataChange callback staleness to detect silent connection failures.
### Acceptance Criteria
- Subscribe to a configurable probe tag (a known-good Galaxy attribute that changes periodically).
- Track `_lastProbeValueTime` (UTC) updated on each OnDataChange for the probe tag.
- If `DateTime.UtcNow - _lastProbeValueTime > staleThreshold`, force disconnect and reconnect.
- Probe tag address: configurable via `MxAccess:ProbeTag`. If not configured, probe monitoring is disabled.
- Stale threshold: configurable via `MxAccess:ProbeStaleThresholdSeconds`, default 60 seconds.
### Details
- The probe tag should be an attribute that the Galaxy runtime updates regularly (e.g., a platform heartbeat or area-level timestamp). The specific tag is site-dependent.
- After forced reconnect, reset `_lastProbeValueTime` to `DateTime.UtcNow` to give the new connection a full threshold window.
---
## MXA-007: COM Cleanup
On disconnect or disposal, the client shall unwire event handlers, unadvise/remove all items, unregister, and release COM objects via Marshal.ReleaseComObject.
### Acceptance Criteria
- Cleanup order: UnAdvise all active subscriptions → RemoveItem all items → unwire OnDataChange and OnWriteComplete event handlers → Unregister → `Marshal.ReleaseComObject`.
- On dispose: run disconnect if still connected, then dispose STA thread.
- Each cleanup step is wrapped in try/catch (cleanup must not throw).
- After cleanup: handle maps are cleared, pending write TCS entries are abandoned, COM reference is set to null.
### Details
- `_storedSubscriptions` is NOT cleared on disconnect (preserved for reconnect replay). Only cleared on Dispose.
- Event handlers must be unwired BEFORE Unregister, or callbacks may fire on a dead object.
- `Marshal.ReleaseComObject` in a finally block, always, even if earlier steps fail.
---
## MXA-008: Operation Metrics
The MXAccess client shall record timing and success/failure for Read, Write, and Subscribe operations.
### Acceptance Criteria
- Each operation records: duration (ms), success/failure.
- Metrics are available for the status dashboard: count, success rate, avg/min/max/P95 latency.
- Uses a rolling 1000-entry buffer for percentile calculation.
- Metrics are exposed via a queryable interface consumed by the status report service.
### Details
- Uses an `ITimingScope` pattern: `using (var scope = metrics.BeginOperation("read")) { ... }` for automatic timing and success tracking.
- Metrics are periodically logged at Debug level for diagnostics.
---
## MXA-009: Error Code Translation
The client shall translate known MXAccess error codes from MXSTATUS_PROXY.detail into human-readable messages for logging and OPC UA status propagation.
### Acceptance Criteria
- Error 1008 → "User lacks security permission"
- Error 1012 → "Secured write required (one signature)"
- Error 1013 → "Verified write required (two signatures)"
- Unknown error codes are logged with their numeric value.
- Translated messages are included in OPC UA StatusCode descriptions and log entries.

View File

@@ -0,0 +1,229 @@
# OPC UA Server — Component Requirements
Parent: [HLR-001](HighLevelReqs.md#hlr-001-opc-ua-server), [HLR-002](HighLevelReqs.md#hlr-002-galaxy-hierarchy-as-opc-ua-address-space), [HLR-004](HighLevelReqs.md#hlr-004-data-type-mapping)
## OPC-001: Server Endpoint
The OPC UA server shall listen on a configurable TCP port (default 4840) using the OPC Foundation .NET Standard stack.
### Acceptance Criteria
- Server starts and accepts TCP connections on the configured port.
- Port is read from `appsettings.json` under `OpcUa:Port`; defaults to 4840 if absent.
- Endpoint URL format: `opc.tcp://<hostname>:<port>/LmxOpcUa`.
- If the port is in use at startup, log an Error and fail to start (do not silently pick another port).
- Security policy: None (no certificate validation). This is an internal plant-floor service.
### Details
- Configurable items: port (default 4840), endpoint path (default `/LmxOpcUa`), server application name (default `LmxOpcUa`).
- Server shall use the `OPCFoundation.NetStandard.Opc.Ua.Server` NuGet package.
- On startup, log the endpoint URL at Information level.
---
## OPC-002: Address Space Structure
The server shall create folder nodes for areas and object nodes for automation objects, organized in the same parent-child hierarchy as the Galaxy.
### Acceptance Criteria
- The root folder node has BrowseName `ZB` (hardcoded Galaxy name).
- Objects where `is_area = 1` are created as FolderType nodes (organizational).
- Objects where `is_area = 0` are created as BaseObjectType nodes.
- Parent-child relationships use Organizes references (for areas) and HasComponent references (for contained objects).
- A client browsing Root → Objects → ZB → DEV → TestArea → TestMachine_001 → DelmiaReceiver sees the same structure as `gr/layout.md`.
### Details
- NodeIds use a string-based identifier scheme: `ns=1;s=<tag_name>` for object nodes, `ns=1;s=<tag_name>.<attribute_name>` for variable nodes.
- Infrastructure objects (AppEngines, Platforms) are included in the tree but may have no variable children.
- When `contained_name` is null or empty, fall back to `tag_name` as the BrowseName.
---
## OPC-003: Variable Nodes for Attributes
Each user-defined attribute on a deployed object shall be represented as an OPC UA variable node under its parent object node.
### Acceptance Criteria
- Each row from `attributes.sql` creates one variable node under the matching object node (matched by `gobject_id`).
- Variable node BrowseName and DisplayName are set to `attribute_name`.
- Variable node stores `full_tag_reference` as its runtime MXAccess address.
- Variable nodes have AccessLevel = CurrentRead | CurrentWrite (3) by default.
- Objects with no user-defined attributes still appear as object nodes with zero children.
### Details
- Security classification from the attributes query is noted but not enforced at the OPC UA level (Galaxy runtime handles security).
- Attributes whose names start with `_` are already filtered by the SQL query.
---
## OPC-004: Browse Name Translation
Browse names shall use contained names (human-readable, scoped to parent). The server shall internally translate browse paths to tag_name references for MXAccess operations.
### Acceptance Criteria
- A variable node browsed as `ZB/DEV/TestArea/TestMachine_001/DelmiaReceiver/DownloadPath` correctly translates to MXAccess reference `DelmiaReceiver_001.DownloadPath`.
- Translation uses the `tag_name` stored on the parent object node, not the browse path.
- No runtime path parsing — the mapping is baked into each node at build time.
### Details
- Each variable node stores its `full_tag_reference` (e.g., `DelmiaReceiver_001.DownloadPath`) at address-space build time. Read/write operations use this stored reference directly.
---
## OPC-005: Data Type Mapping
Variable nodes shall use OPC UA data types mapped from Galaxy mx_data_type values per the mapping in `gr/data_type_mapping.md`.
### Acceptance Criteria
- Every `mx_data_type` value in the mapping table produces the correct OPC UA DataType NodeId on the variable node.
- Unknown/unmapped `mx_data_type` values default to String (i=12).
- ElapsedTime (type 7) maps to Double representing seconds.
### Details
- Full mapping table in `gr/data_type_mapping.md`.
- DateTime conversion: Galaxy may store local time; convert to UTC for OPC UA.
- LocalizedText (type 15): use empty locale string with the text value.
---
## OPC-006: Array Support
Attributes marked as arrays shall have ValueRank=1 and ArrayDimensions set to the attribute's array_dimension value.
### Acceptance Criteria
- `is_array = 1` produces ValueRank = 1 (OneDimension) and ArrayDimensions = `[array_dimension]`.
- `is_array = 0` produces ValueRank = -1 (Scalar) and no ArrayDimensions.
- MXAccess reference for array attributes uses `tag_name.attribute[]` (whole array) format.
### Details
- Individual array element access (`tag_name.attribute[n]`) is not required for initial implementation. Whole-array read/write only.
- If `array_dimension` is null or 0 when `is_array = 1`, log a Warning and default to ArrayDimensions = [0] (variable-length).
---
## OPC-007: Read Operations
The server shall fulfill OPC UA Read requests by reading the corresponding tag value from MXAccess using the tag_name.AttributeName reference.
### Acceptance Criteria
- OPC UA Read request for a variable node results in a read via MXAccess using the node's stored `full_tag_reference`.
- Returned value is converted from the COM variant to the OPC UA data type specified on the node.
- OPC UA StatusCode reflects MXAccess quality: Good maps to Good, Bad/Uncertain map appropriately.
- If MXAccess is not connected, return StatusCode = Bad_NotConnected.
- Read timeout: configurable, default 5 seconds. On timeout, return Bad_Timeout.
### Details
- Prefer cached subscription-delivered values over on-demand reads to reduce COM round-trips.
- If no subscription is active for the tag, perform an on-demand read (AddItem, AdviseSupervisory, wait for first OnDataChange, then UnAdvise/RemoveItem).
- Concurrency: semaphore-limited to configurable max (default 10) concurrent MXAccess operations.
---
## OPC-008: Write Operations
The server shall fulfill OPC UA Write requests by writing to the corresponding tag via MXAccess.
### Acceptance Criteria
- OPC UA Write request results in an MXAccess `Write()` call with completion confirmed via `OnWriteComplete()` callback.
- Write timeout: configurable, default 5 seconds. On timeout, log Warning and return Bad_Timeout.
- MXSTATUS_PROXY with `success = 0` causes the OPC UA write to return Bad_InternalError with the detail message.
- MXAccess errors 1008 (no permission), 1012 (secured write), 1013 (verified write) return Bad_UserAccessDenied.
- Write to a non-existent tag returns Bad_NodeIdUnknown.
- The server shall attempt to convert the written value to the expected Galaxy data type before passing to Write().
### Details
- Write uses security classification -1 (no security). Galaxy runtime handles security enforcement.
- Write sequence: uses existing subscription handle if available, otherwise AddItem + AdviseSupervisory + Write + await OnWriteComplete + cleanup.
- Concurrent write limit: same semaphore as reads (configurable, default 10).
---
## OPC-009: Subscriptions
The server shall support OPC UA subscriptions by mapping them to MXAccess advisory subscriptions and forwarding data change notifications.
### Acceptance Criteria
- OPC UA CreateMonitoredItems results in MXAccess `AdviseSupervisory()` subscriptions for the requested tags.
- Data changes from `OnDataChange` callback are forwarded as OPC UA notifications to all subscribed clients.
- Shared subscriptions: if two OPC UA clients subscribe to the same tag, only one MXAccess subscription exists (ref-counted).
- Last subscriber unsubscribing triggers UnAdvise/RemoveItem on the MXAccess side.
- After MXAccess reconnect, all active MXAccess subscriptions are re-established automatically.
### Details
- Publishing interval from the OPC UA subscription request is honored on the OPC UA side; MXAccess delivers changes as fast as it receives them.
- OPC UA quality mapping from MXAccess quality integers: 192+ = Good, 64-191 = Uncertain, 0-63 = Bad.
- OnDataChange with MXSTATUS_PROXY failure: deliver notification with Bad quality to subscribed clients.
---
## OPC-010: Address Space Rebuild
When a Galaxy deployment change is detected, the server shall rebuild the address space without dropping existing OPC UA client connections where possible.
### Acceptance Criteria
- When Galaxy Repository detects a deployment change, the OPC UA address space is rebuilt.
- Existing OPC UA client sessions are preserved — clients stay connected.
- Subscriptions for tags that still exist after rebuild continue to work.
- Subscriptions for tags that no longer exist receive a Bad_NodeIdUnknown status notification.
- Rebuild is logged at Information level with timing (duration).
### Details
- Rebuild is a full replace, not an incremental diff. Re-query hierarchy and attributes, build new tree, swap atomically.
- During rebuild, reads/writes against the old address space may fail briefly. This is acceptable.
- New MXAccess subscriptions for new tags are established; removed tags are unsubscribed.
---
## OPC-011: Server Diagnostics Node
The server shall expose a ServerStatus node under the standard OPC UA Server object with ServerState, CurrentTime, and StartTime. This is required by the OPC UA specification for compliant servers.
### Acceptance Criteria
- ServerState reports Running during normal operation.
- CurrentTime returns the server's current UTC time.
- StartTime returns the UTC time when the service started.
---
## OPC-012: Namespace Configuration
The server shall register a namespace URI at namespace index 1. All application-specific NodeIds shall use this namespace.
### Acceptance Criteria
- Namespace URI: `urn:ZB:LmxOpcUa` (Galaxy name is configurable).
- All object and variable NodeIds created from Galaxy data use namespace index 1.
- Standard OPC UA nodes remain in namespace 0.
---
## OPC-013: Session Management
The server shall support multiple concurrent OPC UA client sessions.
### Acceptance Criteria
- Maximum concurrent sessions: configurable, default 100.
- Session timeout: configurable, default 30 minutes of inactivity.
- Expired sessions are cleaned up and their subscriptions removed.
- Session count is reported to the status dashboard.

View File

@@ -0,0 +1,117 @@
# Service Host — Component Requirements
Parent: [HLR-006](HighLevelReqs.md#hlr-006-windows-service-hosting), [HLR-007](HighLevelReqs.md#hlr-007-logging)
## SVC-001: TopShelf Hosting
The application shall use TopShelf for Windows service lifecycle (install, uninstall, start, stop) and interactive console mode for development.
### Acceptance Criteria
- TopShelf HostFactory configures the service with name `LmxOpcUa`, display name `LMX OPC UA Server`.
- Service installs via command line: `ZB.MOM.WW.LmxOpcUa.Host.exe install`.
- Service uninstalls via: `ZB.MOM.WW.LmxOpcUa.Host.exe uninstall`.
- Service runs as LocalSystem account (needed for MXAccess COM access and Windows Auth to SQL Server).
- Interactive console mode (exe with no args) works for development/debugging.
- `StartAutomatically` is set for Windows service registration.
### Details
- Platform target: x86 (32-bit) — required for MXAccess COM interop.
- Service description: "OPC UA server exposing System Platform Galaxy tags via MXAccess."
---
## SVC-002: Serilog Logging
The application shall configure Serilog with a rolling daily file sink and console sink, with log files retained for a configurable number of days (default 31).
### Acceptance Criteria
- Console sink active (for interactive/debug mode).
- Rolling daily file sink writing to `logs/lmxopcua-YYYYMMDD.log`.
- Retained file count: configurable, default 31 days.
- Minimum log level: configurable, default Information.
- Log file path: configurable, default `logs/lmxopcua-.log`.
- Serilog is initialized before any other component (first thing in Main).
- `Log.CloseAndFlush()` called in finally block on exit.
### Details
- Structured logging with Serilog message templates (not string.Format).
- Log output includes timestamp, level, source context, message, and exception.
- Fatal exceptions are caught at the top level and logged before exit.
---
## SVC-003: Configuration
The application shall load configuration from appsettings.json with support for environment-specific overrides (appsettings.*.json) and environment variables.
### Acceptance Criteria
- `appsettings.json` is the primary configuration file.
- Environment-specific overrides via `appsettings.{environment}.json`.
- Configuration sections: `OpcUa`, `MxAccess`, `GalaxyRepository`, `Dashboard`.
- Missing optional configuration keys use documented defaults (service does not crash).
- Invalid configuration (e.g., port = -1) is detected at startup with a clear error message.
### Details
- Config is loaded once at startup. No hot-reload (service restart required for config changes). This is appropriate for an industrial service.
- All configurable values and their defaults are documented in `appsettings.json`.
---
## SVC-004: Graceful Shutdown
On service stop, the application shall gracefully shut down all components and flush logs before exiting.
### Acceptance Criteria
- TopShelf WhenStopped triggers orderly shutdown.
- Shutdown sequence: (1) stop change detection polling, (2) stop OPC UA server (stop accepting new sessions, complete pending operations), (3) disconnect MXAccess (cleanup all COM objects), (4) stop status dashboard HTTP listener, (5) flush Serilog.
- Shutdown completes within 30 seconds (Windows SCM timeout).
- All IDisposable components are disposed in reverse-creation order.
### Details
- `CancellationTokenSource` signals all background loops (monitor, change detection, HTTP listener) to stop.
- Log "Service shutdown complete" at Information level as the final log entry before flush.
---
## SVC-005: Startup Sequence
The service shall start components in a defined order, with failure handling at each step.
### Acceptance Criteria
- Startup sequence:
1. Load configuration
2. Initialize Serilog
3. Start STA thread
4. Connect to MXAccess
5. Query Galaxy Repository for initial build
6. Build OPC UA address space
7. Start OPC UA server listener
8. Start change detection polling
9. Start status dashboard HTTP listener
- Failure in steps 1-4 prevents startup (service fails to start).
- Failure in steps 5-9 logs Error but allows the service to run in degraded mode.
### Details
- Degraded mode means the service is running but may have an empty address space (waiting for Galaxy DB) or no dashboard (port conflict). MXAccess connection is the minimum required for the service to be useful.
---
## SVC-006: Unhandled Exception Handling
The service shall handle unexpected crashes gracefully.
### Acceptance Criteria
- Register `AppDomain.CurrentDomain.UnhandledException` handler that logs Fatal before the process terminates.
- TopShelf service recovery is configured: restart on failure with 60-second delay.
- Fatal-level log entry includes the full exception details.

View File

@@ -0,0 +1,157 @@
# Status Dashboard — Component Requirements
Parent: [HLR-009](HighLevelReqs.md#hlr-009-status-dashboard)
Reference: LmxProxy Status Dashboard (see `dashboard.JPG` in project root).
## DASH-001: Embedded HTTP Endpoint
The service shall host a lightweight HTTP listener on a configurable port serving a self-contained HTML status dashboard page (no external dependencies).
### Acceptance Criteria
- Uses `System.Net.HttpListener` on a configurable port (`Dashboard:Port`, default 8081).
- Routes:
- `GET /` → HTML dashboard
- `GET /api/status` → JSON status report
- `GET /api/health` → 200 OK if healthy, 503 if unhealthy
- Only GET requests accepted; other methods return 405.
- Unknown paths return 404.
- All responses include `Cache-Control: no-cache, no-store, must-revalidate` headers.
- Dashboard can be disabled via config (`Dashboard:Enabled`, default true).
### Details
- HTTP prefix: `http://+:{port}/` to bind to all interfaces.
- If HttpListener fails to start (port conflict, missing URL reservation), log Error and continue service startup without the dashboard.
- HTML page is self-contained: inline CSS, no external resources (no CDN, no JavaScript frameworks).
---
## DASH-002: Connection Panel
The dashboard shall display a Connection panel showing MXAccess connection state.
### Acceptance Criteria
- Shows: **Connected** (True/False), **State** (Connected/Disconnected/Reconnecting/Error), **Connected Since** (UTC timestamp).
- Green left border when Connected, red when Disconnected/Error, yellow when Reconnecting.
- "Connected Since" shows "N/A" when not connected.
- Data sourced from MXAccess client's connection state properties.
### Details
- Timestamp format: `yyyy-MM-dd HH:mm:ss UTC`.
- Panel title: "Connection".
---
## DASH-003: Health Panel
The dashboard shall display a Health panel showing overall service health.
### Acceptance Criteria
- Three states: **Healthy** (green text), **Degraded** (yellow text), **Unhealthy** (red text).
- Includes a health message string explaining the status.
- Health rules:
- Not connected to MXAccess → Unhealthy
- Success rate < 50% with > 100 total operations → Degraded
- Connected with acceptable success rate → Healthy
### Details
- Health message examples: "LmxOpcUa is healthy", "MXAccess client is not connected", "Average success rate is below 50%".
- Green left border for Healthy, yellow for Degraded, red for Unhealthy.
---
## DASH-004: Subscriptions Panel
The dashboard shall display a Subscriptions panel showing subscription statistics.
### Acceptance Criteria
- Shows: **Clients** (connected OPC UA client count), **Tags** (total variable nodes in address space), **Active** (active MXAccess subscriptions), **Delivered** (cumulative data change notifications delivered).
- Values update on each dashboard refresh.
- Zero values shown as "0", not blank.
### Details
- "Tags" is the count of variable nodes, not object/folder nodes.
- "Active" is the count of distinct MXAccess item subscriptions (after ref-counting — the number of actual AdviseSupervisory calls, not the number of OPC UA monitored items).
- "Delivered" is a running counter since service start (not reset on reconnect).
---
## DASH-005: Operations Table
The dashboard shall display an operations metrics table showing performance statistics.
### Acceptance Criteria
- Table with columns: **Operation**, **Count**, **Success Rate**, **Avg (ms)**, **Min (ms)**, **Max (ms)**, **P95 (ms)**.
- Rows: Read, Write, Subscribe, Browse.
- Empty cells show em-dash ("—") when no data available (count = 0).
- Success rate displayed as percentage (e.g., "99.8%").
- Latency values rounded to 1 decimal place.
### Details
- Metrics sourced from the PerformanceMetrics component (1000-entry rolling buffer for percentile calculation).
- "Browse" row tracks OPC UA browse operations.
- "Subscribe" row tracks OPC UA CreateMonitoredItems operations.
---
## DASH-006: Footer
The dashboard shall display a footer with last-updated time and service identification.
### Acceptance Criteria
- Format: "Last updated: {timestamp} UTC | Service: ZB.MOM.WW.LmxOpcUa.Host v{version}".
- Timestamp is the server-side UTC time when the HTML was generated.
- Version is read from the assembly version (`Assembly.GetExecutingAssembly().GetName().Version`).
---
## DASH-007: Auto-Refresh
The dashboard page shall auto-refresh to show current status without manual reload.
### Acceptance Criteria
- HTML page includes `<meta http-equiv="refresh" content="10">` for 10-second auto-refresh.
- No JavaScript required for refresh (pure HTML meta-refresh).
- Refresh interval: configurable via `Dashboard:RefreshIntervalSeconds`, default 10 seconds.
---
## DASH-008: JSON Status API
The `/api/status` endpoint shall return a JSON object with all dashboard data for programmatic consumption.
### Acceptance Criteria
- Response Content-Type: `application/json`.
- JSON structure includes: connection state, health status, subscription statistics, and operation metrics.
- Same data as the HTML dashboard, structured for machine consumption.
- Suitable for integration with external monitoring tools.
---
## DASH-009: Galaxy Info Panel
The dashboard shall display a Galaxy Info panel showing Galaxy Repository state.
### Acceptance Criteria
- Shows: **Galaxy Name** (e.g., ZB), **DB Status** (Connected/Disconnected), **Last Deploy** (timestamp from `galaxy.time_of_last_deploy`), **Objects** (count), **Attributes** (count), **Last Rebuild** (timestamp of last address space rebuild).
- Provides visibility into the Galaxy Repository component's state independently of MXAccess connection status.
### Details
- "DB Status" reflects whether the most recent change detection poll succeeded.
- "Last Deploy" shows the raw `time_of_last_deploy` value from the Galaxy database.
- "Objects" and "Attributes" show counts from the most recent successful hierarchy/attribute query.

51
gr/CLAUDE.md Normal file
View File

@@ -0,0 +1,51 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Purpose
The goal of this project is to identify and develop SQL queries that extract the Galaxy object hierarchy from the **System Platform Galaxy Repository** database in order to build a tag structure for an OPC UA server.
Specifically, we need to:
- Build the hierarchy of **areas** and **automation objects** (using contained names for human-readable browsing)
- Translate contained names to **tag_names** for read/write operations (e.g., `TestMachine_001.DelmiaReceiver` in the hierarchy becomes `DelmiaReceiver_001` when addressing tag values)
See `layout.md` for details on the hierarchy vs tag name relationship.
## Key Files
### Documentation
- `connectioninfo.md` — Database connection details and sqlcmd usage
- `layout.md` — Galaxy object hierarchy, contained_name vs tag_name translation, and target OPC UA structure
- `build_layout_plan.md` — Step-by-step plan for extracting hierarchy, attaching attributes, and monitoring for changes
- `data_type_mapping.md` — Galaxy mx_data_type to OPC UA DataType mapping, including array handling (ValueRank, ArrayDimensions)
### Queries
- `queries/hierarchy.sql` — Deployed object hierarchy with browse names and parent relationships
- `queries/attributes.sql` — User-defined (dynamic) attributes with data types and array dimensions
- `queries/attributes_extended.sql` — All attributes (system + user-defined) with data types and array dimensions
- `queries/change_detection.sql` — Poll `galaxy.time_of_last_deploy` to detect deployment changes
### Schema Reference
- `schema.md` — Full schema reference for all tables and views in the ZB database
- `ddl/tables/` — Individual CREATE TABLE definitions
- `ddl/views/` — Individual view definitions
## Working with the Galaxy Repository Database
The Galaxy Repository is the backing SQL Server database for Wonderware/AVEVA System Platform (Galaxy: ZB, localhost, Windows Auth). Key tables used by the queries:
- **gobject** — Object instances, hierarchy (contained_by_gobject_id, area_gobject_id), deployment state (deployed_package_id)
- **template_definition** — Object type categories (category_id distinguishes areas, engines, user-defined objects, etc.)
- **dynamic_attribute** — User-defined attributes on templates, inherited by instances via derived_from_gobject_id chain
- **attribute_definition** — System/primitive attributes
- **primitive_instance** — Links objects to their primitive components and attribute definitions
- **galaxy** — Single-row table with time_of_last_deploy for change detection
Use `sqlcmd -S localhost -d ZB -E -Q "..."` to run queries. See `connectioninfo.md` for details.
## Conventions
- Store all connection parameters in `connectioninfo.md`, not scattered across scripts.
- Keep SQL query examples and extraction notes as Markdown files in this repo.
- If scripts are added (Python, PowerShell, etc.), document their usage and dependencies alongside them.

84
gr/build_layout_plan.md Normal file
View File

@@ -0,0 +1,84 @@
# OPC UA Server Layout — Build Plan
## Overview
Extract the Galaxy object hierarchy and tag definitions from the ZB (Galaxy Repository) database to construct an OPC UA server address space. The root node is hardcoded as **ZB**.
## Step 1: Build the Browse Tree
Run `queries/hierarchy.sql` to get all deployed automation objects and their parent-child relationships.
For each row returned:
- `parent_gobject_id = 0` → child of the root ZB node
- `is_area = 1` → create as an OPC UA folder node (organizational)
- `is_area = 0` → create as an OPC UA object node (container for tags)
- Use `browse_name` as the OPC UA BrowseName/DisplayName
- Store `gobject_id` and `tag_name` for attribute lookup and tag reference translation
Build the tree by matching each row's `parent_gobject_id` to another row's `gobject_id`. The result is:
```
ZB (root, hardcoded)
└── DEV (folder, is_area=1)
├── DevAppEngine (object)
├── DevPlatform (object)
└── TestArea (folder, is_area=1)
├── DevTestObject (object)
└── TestMachine_001 (object)
├── DelmiaReceiver (object, browse_name from contained_name)
└── MESReceiver (object, browse_name from contained_name)
```
## Step 2: Attach Attributes as Tag Nodes
Run `queries/attributes.sql` to get all user-defined attributes for deployed objects.
For each attribute row:
- Match to the browse tree via `gobject_id`
- Create an OPC UA variable node under the matching object node
- Use `attribute_name` as the BrowseName/DisplayName
- Use `full_tag_reference` as the runtime tag path for read/write operations
- Map `mx_data_type` to OPC UA built-in types:
| mx_data_type | Description | OPC UA Type |
|--------------|-------------|-------------|
| 1 | Boolean | Boolean |
| 2 | Integer | Int32 |
| 3 | Float | Float |
| 4 | Double | Double |
| 5 | String | String |
| 6 | Time | DateTime |
| 7 | ElapsedTime | Double (seconds) or Duration |
- If `is_array = 1`, create the variable as an array with rank 1 and dimension from `array_dimension`
## Step 3: Monitor for Changes
Poll `queries/change_detection.sql` on a regular interval (e.g., every 30 seconds).
```
SELECT time_of_last_deploy FROM galaxy;
```
Compare the returned `time_of_last_deploy` to the last known value:
- **No change** → do nothing
- **Changed** → a deployment occurred; re-run Steps 1 and 2 to rebuild the address space
This handles objects being deployed, undeployed, added, or removed.
## Connection Details
See `connectioninfo.md` for database connection parameters and sqlcmd usage.
```
sqlcmd -S localhost -d ZB -E -Q "YOUR QUERY HERE"
```
## Query Files
| File | Purpose |
|------|---------|
| `queries/hierarchy.sql` | Deployed object hierarchy with browse names and parent relationships |
| `queries/attributes.sql` | User-defined attributes with data types and array dimensions |
| `queries/attributes_extended.sql` | All attributes (system + user-defined) with data types and array dimensions |
| `queries/change_detection.sql` | Poll galaxy.time_of_last_deploy for deployment changes |

26
gr/connectioninfo.md Normal file
View File

@@ -0,0 +1,26 @@
# Galaxy Repository — Connection Information
## Database Connection
| Parameter | Value |
|-----------------|----------------|
| Server | localhost (default instance) |
| Database Name | ZB |
| Port | 1433 (default) |
| Authentication | Windows Auth |
| Username | dohertj2 |
## sqlcmd Usage
```
sqlcmd -S localhost -d ZB -E -Q "YOUR QUERY HERE"
```
- `-S localhost` — default instance
- `-d ZB` — database name
- `-E` — Windows Authentication (dohertj2)
## Notes
- The Galaxy Repository is a SQL Server database created and managed by AVEVA System Platform (formerly Wonderware).
- Typically accessed via SQL Server Management Studio (SSMS), `sqlcmd`, or programmatically via ODBC/ADO.NET/pyodbc.

80
gr/data_type_mapping.md Normal file
View File

@@ -0,0 +1,80 @@
# Data Type Mapping — Galaxy Repository to OPC UA
## Scalar Type Mapping
| mx_data_type | Galaxy Description | OPC UA DataType | OPC UA NodeId | Notes |
|--------------|--------------------|-----------------|---------------|-------|
| 1 | Boolean | Boolean | i=1 | Direct mapping |
| 2 | Integer (Int32) | Int32 | i=6 | Galaxy integers are 32-bit signed |
| 3 | Float (Single) | Float | i=10 | 32-bit IEEE 754 |
| 4 | Double | Double | i=11 | 64-bit IEEE 754 |
| 5 | String | String | i=12 | Unicode string |
| 6 | Time (DateTime) | DateTime | i=13 | Galaxy DateTime to OPC UA DateTime (100ns ticks since 1601-01-01) |
| 7 | ElapsedTime (TimeSpan) | Double | i=11 | No native OPC UA TimeSpan; map to Double representing seconds (or use Duration type alias, NodeId i=290) |
| 8 | (reference) | String | i=12 | Object reference; expose as string representation |
| 13 | (enumeration) | Int32 | i=6 | Enum backing value is integer |
| 14 | (custom) | String | i=12 | Fallback to string |
| 15 | InternationalizedString | LocalizedText | i=21 | OPC UA LocalizedText supports locale + text pairs |
| 16 | (custom) | String | i=12 | Fallback to string |
## OPC UA Built-in Type Reference
For context, the full set of OPC UA built-in types and their NodeIds:
| NodeId | Type | Description |
|--------|------|-------------|
| i=1 | Boolean | True/false |
| i=2 | SByte | Signed 8-bit integer |
| i=3 | Byte | Unsigned 8-bit integer |
| i=4 | Int16 | Signed 16-bit integer |
| i=5 | UInt16 | Unsigned 16-bit integer |
| i=6 | Int32 | Signed 32-bit integer |
| i=7 | UInt32 | Unsigned 32-bit integer |
| i=8 | Int64 | Signed 64-bit integer |
| i=9 | UInt64 | Unsigned 64-bit integer |
| i=10 | Float | 32-bit IEEE 754 |
| i=11 | Double | 64-bit IEEE 754 |
| i=12 | String | Unicode string |
| i=13 | DateTime | Date and time (100ns ticks since 1601-01-01) |
| i=14 | Guid | 128-bit globally unique identifier |
| i=15 | ByteString | Sequence of bytes |
| i=21 | LocalizedText | Locale + text pair |
## Array Handling
When `is_array = 1` in the attributes query, the OPC UA variable node must be configured as an array.
### ValueRank
Set on the OPC UA variable node to indicate scalar vs array:
| is_array | ValueRank | Meaning |
|----------|-----------|---------|
| 0 | -1 (Scalar) | Value is not an array |
| 1 | 1 (OneDimension) | Value is a one-dimensional array |
### ArrayDimensions
When `ValueRank = 1`, set the `ArrayDimensions` attribute to a single-element array containing the `array_dimension` value from the attributes query.
Example for `MESReceiver_001.MoveInPartNumbers` (`is_array=1`, `array_dimension=50`):
- DataType: String (i=12)
- ValueRank: 1
- ArrayDimensions: [50]
Example for `TestMachine_001.MachineID` (`is_array=0`):
- DataType: String (i=12)
- ValueRank: -1
- ArrayDimensions: (not set)
## DateTime Conversion
Galaxy `Time` (mx_data_type=6) stores DateTime values. OPC UA DateTime is defined as the number of 100-nanosecond intervals since January 1, 1601 (UTC). Ensure the conversion accounts for:
- Timezone: Galaxy may store local time; OPC UA expects UTC
- Epoch difference: adjust if Galaxy uses a different epoch (e.g., Unix epoch 1970-01-01)
## ElapsedTime Handling
Galaxy `ElapsedTime` (mx_data_type=7) represents a duration/timespan. OPC UA has no native TimeSpan type. Options:
- **Double (i=11)**: Store as seconds (recommended for simplicity)
- **Duration (i=290)**: OPC UA type alias for Double, semantically represents milliseconds — use if the OPC UA SDK supports it

View File

@@ -0,0 +1,13 @@
-- Table: ConversionQueue
CREATE TABLE [ConversionQueue] (
[id] int NULL,
[Name] nvarchar(329) NULL,
[IsCheckedOut] bit NOT NULL,
[Status] bit NOT NULL DEFAULT ((0)),
[MetaData] nchar(256) NULL,
[OperationType] nchar(20) NOT NULL,
[timestamp_of_last_change] bigint NULL,
[change_type] int NULL
);
GO

View File

@@ -0,0 +1,9 @@
-- Table: CurrentSessionContainedName
CREATE TABLE [CurrentSessionContainedName] (
[Uniqeid] int NOT NULL,
[obj_id] int NULL,
[containedname] nvarchar(32) NULL,
CONSTRAINT [PK_CurrentSessionContainedName] PRIMARY KEY ([Uniqeid])
);
GO

View File

@@ -0,0 +1,7 @@
-- Table: ImportTransaction
CREATE TABLE [ImportTransaction] (
[ImportOperationId] nvarchar(329) NULL,
[Status] bit NOT NULL DEFAULT ((1))
);
GO

View File

@@ -0,0 +1,8 @@
-- Table: aa_sql_objects
CREATE TABLE [aa_sql_objects] (
[object_name] nvarchar(128) NOT NULL,
[object_type] nvarchar(10) NOT NULL,
CONSTRAINT [PK_aa_sql_objects] PRIMARY KEY ([object_name])
);
GO

View File

@@ -0,0 +1,9 @@
-- Table: affected_overview_symbols
CREATE TABLE [affected_overview_symbols] (
[gobject_id] int NOT NULL,
[package_id] int NOT NULL,
[mx_primitive_id] smallint NOT NULL,
[visual_element_id] int NOT NULL
);
GO

View File

@@ -0,0 +1,8 @@
-- Table: alarm_message_defaults
CREATE TABLE [alarm_message_defaults] (
[phrase_id] int NOT NULL,
[default_message] nvarchar(1024) NOT NULL,
CONSTRAINT [PK_alarm_message_defaults] PRIMARY KEY ([phrase_id])
);
GO

View File

@@ -0,0 +1,8 @@
-- Table: alarm_message_timestamps
CREATE TABLE [alarm_message_timestamps] (
[gobject_id] int NOT NULL,
[timestamp_of_populate] bigint NOT NULL DEFAULT ((0)),
CONSTRAINT [PK_alarm_message_timestamps] PRIMARY KEY ([gobject_id])
);
GO

View File

@@ -0,0 +1,12 @@
-- Table: alarm_message_translations
CREATE TABLE [alarm_message_translations] (
[phrase_id] int NOT NULL,
[locale_id] smallint NOT NULL,
[translated_message] nvarchar(1024) NOT NULL,
CONSTRAINT [PK_alarm_message_translations] PRIMARY KEY ([phrase_id], [locale_id], [phrase_id], [locale_id])
);
GO
ALTER TABLE [alarm_message_translations] ADD FOREIGN KEY ([locale_id]) REFERENCES [supported_locales] ([locale_id]);
GO

View File

@@ -0,0 +1,13 @@
-- Table: alarm_messages
CREATE TABLE [alarm_messages] (
[gobject_id] int NOT NULL,
[package_id] int NOT NULL,
[mx_primitive_id] smallint NOT NULL,
[phrase_id] int NOT NULL,
CONSTRAINT [PK_alarm_messages] PRIMARY KEY ([gobject_id], [package_id], [mx_primitive_id], [phrase_id], [gobject_id], [gobject_id], [mx_primitive_id], [package_id], [gobject_id], [mx_primitive_id], [package_id], [gobject_id], [mx_primitive_id], [package_id])
);
GO
ALTER TABLE [alarm_messages] ADD FOREIGN KEY ([package_id]) REFERENCES [primitive_instance] ([package_id]);
GO

View File

@@ -0,0 +1,24 @@
-- Table: attribute_definition
CREATE TABLE [attribute_definition] (
[attribute_definition_id] int NOT NULL,
[primitive_definition_id] int NOT NULL,
[attribute_name] nvarchar(329) NOT NULL,
[mx_attribute_id] smallint NOT NULL,
[has_config_set_handler] bit NOT NULL,
[mx_data_type] smallint NOT NULL,
[is_array] bit NOT NULL,
[security_classification] smallint NOT NULL,
[security_classification_needs_deployed] bit NOT NULL,
[mx_attribute_category] int NOT NULL,
[is_frequently_accessed] bit NOT NULL,
[is_locked] bit NOT NULL,
[is_locked_needs_deployed] bit NOT NULL,
[mx_value] text(2147483647) NOT NULL,
[mx_value_needs_deployed] bit NOT NULL,
CONSTRAINT [PK_attribute_definition] PRIMARY KEY ([primitive_definition_id], [mx_attribute_id], [primitive_definition_id])
);
GO
ALTER TABLE [attribute_definition] ADD FOREIGN KEY ([primitive_definition_id]) REFERENCES [primitive_definition] ([primitive_definition_id]);
GO

View File

@@ -0,0 +1,26 @@
-- Table: attribute_reference
CREATE TABLE [attribute_reference] (
[gobject_id] int NOT NULL,
[package_id] int NOT NULL,
[referring_mx_primitive_id] smallint NOT NULL DEFAULT ((0)),
[referring_mx_attribute_id] smallint NOT NULL DEFAULT ((0)),
[element_index] smallint NOT NULL DEFAULT ((0)),
[resolved_gobject_id] int NOT NULL DEFAULT ((0)),
[reference_string] nvarchar(700) NOT NULL DEFAULT (''),
[context_string] nvarchar(329) NOT NULL DEFAULT (''),
[object_signature] int NOT NULL DEFAULT ((0)),
[resolved_mx_primitive_id] smallint NOT NULL DEFAULT ((0)),
[resolved_mx_attribute_id] smallint NOT NULL DEFAULT ((0)),
[resolved_mx_property_id] smallint NOT NULL DEFAULT ((0)),
[attribute_signature] int NOT NULL DEFAULT ((0)),
[lock_type] int NOT NULL DEFAULT ((0)),
[is_valid] bit NOT NULL DEFAULT ((0)),
[attr_res_status] int NOT NULL DEFAULT ((0)),
[attribute_index] smallint NULL DEFAULT ((-1)),
CONSTRAINT [PK_attribute_reference] PRIMARY KEY ([gobject_id], [package_id], [referring_mx_primitive_id], [referring_mx_attribute_id], [element_index], [gobject_id], [package_id], [referring_mx_primitive_id], [gobject_id], [package_id], [referring_mx_primitive_id], [gobject_id], [package_id], [referring_mx_primitive_id])
);
GO
ALTER TABLE [attribute_reference] ADD FOREIGN KEY ([referring_mx_primitive_id]) REFERENCES [primitive_instance] ([package_id]);
GO

View File

@@ -0,0 +1,11 @@
-- Table: attributes_translation_table
CREATE TABLE [attributes_translation_table] (
[gobject_id] int NULL,
[attribute_name] nvarchar(329) NOT NULL,
[new_primitive_id] int NULL,
[new_attribute_id] int NULL,
[old_primitive_id] int NULL,
[old_attribute_id] int NULL
);
GO

View File

@@ -0,0 +1,11 @@
-- Table: autobind_device
CREATE TABLE [autobind_device] (
[dio_id] int NOT NULL,
[overridden_naming_rule_id] int NULL,
CONSTRAINT [PK_autobind_device] PRIMARY KEY ([dio_id], [overridden_naming_rule_id], [dio_id])
);
GO
ALTER TABLE [autobind_device] ADD FOREIGN KEY ([dio_id]) REFERENCES [gobject] ([gobject_id]);
GO

View File

@@ -0,0 +1,11 @@
-- Table: autobind_device_category
CREATE TABLE [autobind_device_category] (
[category_id] smallint NOT NULL,
[rule_id] int NULL DEFAULT ((0)),
CONSTRAINT [PK_autobind_device_category] PRIMARY KEY ([category_id], [rule_id], [category_id])
);
GO
ALTER TABLE [autobind_device_category] ADD FOREIGN KEY ([category_id]) REFERENCES [lookup_category] ([category_id]);
GO

View File

@@ -0,0 +1,11 @@
-- Table: autobind_device_template
CREATE TABLE [autobind_device_template] (
[template_definition_id] int NOT NULL,
[rule_id] int NULL,
CONSTRAINT [PK_autobind_device_template] PRIMARY KEY ([template_definition_id], [rule_id], [template_definition_id])
);
GO
ALTER TABLE [autobind_device_template] ADD FOREIGN KEY ([template_definition_id]) REFERENCES [template_definition] ([template_definition_id]);
GO

View File

@@ -0,0 +1,13 @@
-- Table: autobind_device_topic
CREATE TABLE [autobind_device_topic] (
[dio_id] int NOT NULL,
[sg_mx_primitive_id] smallint NOT NULL DEFAULT ((0)),
[overridden_naming_rule_id] int NULL,
[default_xlate_rule_id] int NOT NULL DEFAULT ((0)),
CONSTRAINT [PK_autobind_device_topic] PRIMARY KEY ([dio_id], [sg_mx_primitive_id], [overridden_naming_rule_id], [dio_id])
);
GO
ALTER TABLE [autobind_device_topic] ADD FOREIGN KEY ([dio_id]) REFERENCES [autobind_device] ([dio_id]);
GO

View File

@@ -0,0 +1,8 @@
-- Table: autobind_naming_rule
CREATE TABLE [autobind_naming_rule] (
[rule_id] int NOT NULL,
[rule_name] nvarchar(329) NOT NULL,
CONSTRAINT [PK_autobind_naming_rule] PRIMARY KEY ([rule_id])
);
GO

View File

@@ -0,0 +1,12 @@
-- Table: autobind_naming_rule_spec
CREATE TABLE [autobind_naming_rule_spec] (
[rule_id] int NOT NULL,
[io_type] nchar(1) NOT NULL,
[rule_spec] nvarchar(512) NOT NULL,
CONSTRAINT [PK_autobind_naming_rule_spec] PRIMARY KEY ([rule_id], [io_type], [rule_id])
);
GO
ALTER TABLE [autobind_naming_rule_spec] ADD FOREIGN KEY ([rule_id]) REFERENCES [autobind_naming_rule] ([rule_id]);
GO

View File

@@ -0,0 +1,10 @@
-- Table: autobind_translation_rule
CREATE TABLE [autobind_translation_rule] (
[xlate_rule_id] int NOT NULL,
[xlate_rule_name] nvarchar(329) NOT NULL,
[xlate_rule_gsub_str] nvarchar(1000) NULL,
[xlate_rule_scope_global] bit NOT NULL DEFAULT ((0)),
CONSTRAINT [PK_autobind_translation_rule] PRIMARY KEY ([xlate_rule_id])
);
GO

View File

@@ -0,0 +1,17 @@
-- Table: autobound_attribute
CREATE TABLE [autobound_attribute] (
[dio_id] int NOT NULL,
[sg_mx_primitive_id] smallint NOT NULL DEFAULT ((0)),
[gobject_id] int NOT NULL,
[mx_primitive_id] smallint NOT NULL,
[mx_attribute_id] smallint NOT NULL,
[element_index] smallint NOT NULL DEFAULT ((0)),
[attr_alias] nvarchar(329) NULL,
[xlate_rule_id] int NOT NULL DEFAULT ((0)),
CONSTRAINT [PK_autobound_attribute] PRIMARY KEY ([gobject_id], [mx_primitive_id], [mx_attribute_id], [element_index], [dio_id], [sg_mx_primitive_id], [dio_id], [sg_mx_primitive_id], [xlate_rule_id])
);
GO
ALTER TABLE [autobound_attribute] ADD FOREIGN KEY ([xlate_rule_id]) REFERENCES [autobind_translation_rule] ([xlate_rule_id]);
GO

View File

@@ -0,0 +1,9 @@
-- Table: client_control_class_link
CREATE TABLE [client_control_class_link] (
[gobject_id] int NOT NULL,
[file_id] int NULL,
[class_name] nvarchar(1024) NOT NULL,
CONSTRAINT [PK_client_control_class_link] PRIMARY KEY ([gobject_id])
);
GO

View File

@@ -0,0 +1,11 @@
-- Table: client_info
CREATE TABLE [client_info] (
[id] int NOT NULL,
[client_unique_identifier] nvarchar(4000) NOT NULL,
[client_name] nvarchar(64) NOT NULL,
[deployed_files_count] smallint NOT NULL,
[time_of_last_deployed_object_components] datetime NULL DEFAULT (getdate()),
[timestamp_of_last_synchronized] bigint NOT NULL DEFAULT ((0))
);
GO

View File

@@ -0,0 +1,16 @@
-- Table: control_index
CREATE TABLE [control_index] (
[entity_id] int NOT NULL,
[gobject_id] int NOT NULL,
[control_id] nvarchar(329) NULL,
[control_name] nvarchar(329) NOT NULL,
[control_description] nvarchar(2000) NULL,
[properties] nvarchar(-1) NULL,
[thumbnail] nvarchar(-1) NULL,
CONSTRAINT [PK_control_index] PRIMARY KEY ([gobject_id], [control_name], [gobject_id])
);
GO
ALTER TABLE [control_index] ADD FOREIGN KEY ([gobject_id]) REFERENCES [gobject] ([gobject_id]);
GO

View File

@@ -0,0 +1,9 @@
-- Table: data_type
CREATE TABLE [data_type] (
[mx_data_type] tinyint NOT NULL,
[description] varchar(30) NOT NULL,
[ow_data_type] varchar(10) NULL,
CONSTRAINT [PK_data_type] PRIMARY KEY ([mx_data_type])
);
GO

View File

@@ -0,0 +1,8 @@
-- Table: deleted_gobject
CREATE TABLE [deleted_gobject] (
[gobject_id] int NOT NULL DEFAULT ((0)),
[timestamp_of_delete] timestamp NOT NULL,
CONSTRAINT [PK_deleted_gobject] PRIMARY KEY ([timestamp_of_delete])
);
GO

View File

@@ -0,0 +1,9 @@
-- Table: deleted_ids
CREATE TABLE [deleted_ids] (
[table_id] smallint NULL,
[deleted_id] int NOT NULL,
[deletion_timestamp] timestamp NOT NULL,
[deletion_time] datetime NULL
);
GO

View File

@@ -0,0 +1,8 @@
-- Table: deleted_visual_element
CREATE TABLE [deleted_visual_element] (
[visual_element_name] nvarchar(329) NULL,
[visual_element_type] nvarchar(32) NULL,
[timestamp_of_delete] timestamp NOT NULL
);
GO

View File

@@ -0,0 +1,13 @@
-- Table: deleted_visual_element_version
CREATE TABLE [deleted_visual_element_version] (
[gobject_id] int NOT NULL,
[package_id] int NOT NULL,
[mx_primitive_id] smallint NOT NULL,
[visual_element_name] nvarchar(329) NOT NULL,
[visual_element_type] nvarchar(32) NOT NULL,
[timestamp_of_delete] timestamp NOT NULL,
[visual_element_id] int NOT NULL,
CONSTRAINT [PK_deleted_visual_element_version] PRIMARY KEY ([gobject_id], [package_id], [timestamp_of_delete])
);
GO

View File

@@ -0,0 +1,19 @@
-- Table: deployed_file
CREATE TABLE [deployed_file] (
[deployed_file_id] int NOT NULL,
[file_id] int NOT NULL,
[node_name] nvarchar(256) NOT NULL,
[need_to_delete] int NOT NULL DEFAULT ((0)),
[is_package_deployed] bit NOT NULL,
[is_editor_deployed] bit NOT NULL,
[is_runtime_deployed] bit NOT NULL,
[is_browser_deployed] bit NOT NULL,
[file_version] nvarchar(50) NOT NULL DEFAULT (''),
[file_modified_time] nvarchar(50) NOT NULL DEFAULT (''),
CONSTRAINT [PK_deployed_file] PRIMARY KEY ([deployed_file_id], [file_id])
);
GO
ALTER TABLE [deployed_file] ADD FOREIGN KEY ([file_id]) REFERENCES [file_table] ([file_id]);
GO

View File

@@ -0,0 +1,8 @@
-- Table: deployed_intouch_viewapp
CREATE TABLE [deployed_intouch_viewapp] (
[timestamp_of_deploy] bigint NOT NULL DEFAULT ((1)),
[gobject_id] int NOT NULL,
[deploy_file_transfering] bit NULL DEFAULT ((0))
);
GO

View File

@@ -0,0 +1,7 @@
-- Table: deployed_intouch_viewapp_visual_element_dependency
CREATE TABLE [deployed_intouch_viewapp_visual_element_dependency] (
[gobject_id] int NULL,
[visual_element_name] nvarchar(2000) NULL
);
GO

View File

@@ -0,0 +1,25 @@
-- Table: dynamic_attribute
CREATE TABLE [dynamic_attribute] (
[gobject_id] int NOT NULL,
[package_id] int NOT NULL,
[mx_primitive_id] smallint NOT NULL,
[mx_attribute_id] smallint NOT NULL,
[attribute_name] nvarchar(329) NOT NULL,
[mx_data_type] smallint NOT NULL,
[is_array] bit NOT NULL,
[security_classification] smallint NOT NULL,
[mx_attribute_category] int NOT NULL,
[lock_type] int NOT NULL,
[mx_value] text(2147483647) NOT NULL,
[owned_by_gobject_id] int NOT NULL DEFAULT ((0)),
[original_lock_type] int NOT NULL DEFAULT ((0)),
[dynamic_attribute_type] smallint NOT NULL DEFAULT ((0)),
[bitvalues] smallint NOT NULL DEFAULT ((0)),
[dynamic_attribute_id] bigint NOT NULL,
CONSTRAINT [PK_dynamic_attribute] PRIMARY KEY ([gobject_id], [package_id], [mx_primitive_id], [mx_attribute_id], [gobject_id], [mx_primitive_id], [package_id], [gobject_id], [mx_primitive_id], [package_id], [gobject_id], [mx_primitive_id], [package_id])
);
GO
ALTER TABLE [dynamic_attribute] ADD FOREIGN KEY ([package_id]) REFERENCES [primitive_instance] ([package_id]);
GO

View File

@@ -0,0 +1,12 @@
-- Table: external_content_media_types
CREATE TABLE [external_content_media_types] (
[entity_id] int NOT NULL,
[media_type] nvarchar(255) NOT NULL,
[control_entity_id] int NOT NULL,
[uri_property_name] nvarchar(1023) NULL,
[media_type_property_name] nvarchar(1023) NULL,
[is_default] bit NULL,
CONSTRAINT [PK_external_content_media_types] PRIMARY KEY ([entity_id])
);
GO

View File

@@ -0,0 +1,9 @@
-- Table: feature
CREATE TABLE [feature] (
[feature_id] int NOT NULL,
[feature_name] nvarchar(256) NOT NULL,
[feature_type] nvarchar(256) NOT NULL,
CONSTRAINT [PK_feature] PRIMARY KEY ([feature_id])
);
GO

View File

@@ -0,0 +1,11 @@
-- Table: feature_file_link
CREATE TABLE [feature_file_link] (
[feature_id] int NOT NULL,
[file_id] int NOT NULL,
CONSTRAINT [PK_feature_file_link] PRIMARY KEY ([feature_id], [file_id], [feature_id], [file_id])
);
GO
ALTER TABLE [feature_file_link] ADD FOREIGN KEY ([file_id]) REFERENCES [file_table] ([file_id]);
GO

View File

@@ -0,0 +1,13 @@
-- Table: file_browserinfo_link
CREATE TABLE [file_browserinfo_link] (
[primitive_definition_id] int NOT NULL,
[file_id] int NOT NULL,
[assembly_strong_name] nvarchar(512) NOT NULL,
[assembly_type_name] nvarchar(256) NOT NULL,
CONSTRAINT [PK_file_browserinfo_link] PRIMARY KEY ([primitive_definition_id], [file_id], [file_id], [primitive_definition_id])
);
GO
ALTER TABLE [file_browserinfo_link] ADD FOREIGN KEY ([primitive_definition_id]) REFERENCES [primitive_definition] ([primitive_definition_id]);
GO

View File

@@ -0,0 +1,11 @@
-- Table: file_pending_update
CREATE TABLE [file_pending_update] (
[file_id] int NOT NULL,
[node_name] nvarchar(256) NOT NULL,
CONSTRAINT [PK_file_pending_update] PRIMARY KEY ([file_id])
);
GO
ALTER TABLE [file_pending_update] ADD FOREIGN KEY ([file_id]) REFERENCES [file_table] ([file_id]);
GO

View File

@@ -0,0 +1,15 @@
-- Table: file_primitive_definition_link
CREATE TABLE [file_primitive_definition_link] (
[primitive_definition_id] int NOT NULL,
[file_id] int NOT NULL,
[is_needed_for_package] bit NOT NULL DEFAULT ((0)),
[is_needed_for_runtime] bit NOT NULL DEFAULT ((0)),
[is_needed_for_editor] bit NOT NULL DEFAULT ((0)),
[is_needed_for_browser] bit NOT NULL DEFAULT ((0)),
CONSTRAINT [PK_file_primitive_definition_link] PRIMARY KEY ([primitive_definition_id], [file_id], [file_id], [primitive_definition_id])
);
GO
ALTER TABLE [file_primitive_definition_link] ADD FOREIGN KEY ([primitive_definition_id]) REFERENCES [primitive_definition] ([primitive_definition_id]);
GO

View File

@@ -0,0 +1,13 @@
-- Table: file_table
CREATE TABLE [file_table] (
[file_id] int NOT NULL,
[file_name] nvarchar(256) NOT NULL,
[vendor_name] nvarchar(256) NOT NULL,
[registration_type] int NOT NULL,
[subfolder] nvarchar(256) NOT NULL DEFAULT (''),
[file_version] nvarchar(50) NOT NULL DEFAULT (''),
[file_modified_time] nvarchar(50) NOT NULL DEFAULT (''),
CONSTRAINT [PK_file_table] PRIMARY KEY ([file_id])
);
GO

14
gr/ddl/tables/folder.sql Normal file
View File

@@ -0,0 +1,14 @@
-- Table: folder
CREATE TABLE [folder] (
[folder_id] int NOT NULL,
[folder_type] smallint NOT NULL,
[folder_name] nvarchar(64) NOT NULL,
[parent_folder_id] int NOT NULL,
[depth] int NOT NULL,
[has_objects] bit NOT NULL,
[has_folders] bit NOT NULL,
[timestamp_of_last_change] timestamp NOT NULL,
CONSTRAINT [PK_folder] PRIMARY KEY ([folder_id])
);
GO

View File

@@ -0,0 +1,13 @@
-- Table: folder_gobject_link
CREATE TABLE [folder_gobject_link] (
[folder_id] int NOT NULL,
[folder_type] smallint NOT NULL,
[gobject_id] int NOT NULL,
[timestamp_of_last_change] timestamp NOT NULL,
CONSTRAINT [PK_folder_gobject_link] PRIMARY KEY ([folder_id], [gobject_id], [gobject_id])
);
GO
ALTER TABLE [folder_gobject_link] ADD FOREIGN KEY ([gobject_id]) REFERENCES [gobject] ([gobject_id]);
GO

18
gr/ddl/tables/galaxy.sql Normal file
View File

@@ -0,0 +1,18 @@
-- Table: galaxy
CREATE TABLE [galaxy] (
[time_of_last_deploy] datetime NULL DEFAULT (getdate()),
[time_of_last_config_change] datetime NULL DEFAULT (getdate()),
[is_galaxy_installed] bit NOT NULL DEFAULT ((1)),
[time_of_last_reference_binding] datetime NULL DEFAULT (getdate()),
[timestamp_of_last_cascade] bigint NOT NULL DEFAULT ((1)),
[timestamp_of_last_visual_element_reference_bind] bigint NOT NULL DEFAULT ((0)),
[max_proxy_timestamp] bigint NOT NULL DEFAULT (CONVERT([bigint],@@dbts)),
[max_visual_element_timestamp] bigint NOT NULL DEFAULT (CONVERT([bigint],@@dbts)),
[is_migration_in_progress] bit NOT NULL DEFAULT ((0)),
[time_of_last_association_change] datetime NULL DEFAULT (getdate()),
[subscription_id] uniqueidentifier NULL,
[batch_id] uniqueidentifier NULL,
[iteration_id] int NOT NULL DEFAULT ((0))
);
GO

View File

@@ -0,0 +1,7 @@
-- Table: galaxy_data
CREATE TABLE [galaxy_data] (
[data_type] nvarchar(256) NOT NULL,
[data] image(2147483647) NULL
);
GO

View File

@@ -0,0 +1,8 @@
-- Table: galaxy_settings
CREATE TABLE [galaxy_settings] (
[galaxyid] int NULL,
[default_qs_data] ntext(1073741823) NOT NULL,
[current_qs_data] ntext(1073741823) NOT NULL
);
GO

35
gr/ddl/tables/gobject.sql Normal file
View File

@@ -0,0 +1,35 @@
-- Table: gobject
CREATE TABLE [gobject] (
[gobject_id] int NOT NULL,
[template_definition_id] int NOT NULL,
[derived_from_gobject_id] int NOT NULL DEFAULT ((0)),
[contained_by_gobject_id] int NOT NULL DEFAULT ((0)),
[area_gobject_id] int NOT NULL DEFAULT ((0)),
[hosted_by_gobject_id] int NOT NULL DEFAULT ((0)),
[checked_out_by_user_guid] uniqueidentifier NULL,
[default_symbol_gobject_id] int NOT NULL DEFAULT ((0)),
[default_display_gobject_id] int NOT NULL DEFAULT ((0)),
[checked_in_package_id] int NOT NULL DEFAULT ((0)),
[checked_out_package_id] int NOT NULL DEFAULT ((0)),
[deployed_package_id] int NOT NULL DEFAULT ((0)),
[last_deployed_package_id] int NOT NULL DEFAULT ((0)),
[tag_name] nvarchar(329) NOT NULL,
[contained_name] nvarchar(32) NOT NULL DEFAULT (''),
[identity_guid] uniqueidentifier NOT NULL DEFAULT (newid()),
[configuration_guid] uniqueidentifier NOT NULL,
[configuration_version] int NOT NULL,
[deployed_version] int NOT NULL DEFAULT ((0)),
[is_template] bit NOT NULL DEFAULT ((0)),
[is_hidden] bit NOT NULL DEFAULT ((0)),
[software_upgrade_needed] bit NOT NULL DEFAULT ((0)),
[hosting_tree_level] smallint NOT NULL DEFAULT ((0)),
[hierarchical_name] nvarchar(329) NOT NULL DEFAULT (''),
[namespace_id] smallint NOT NULL DEFAULT ((1)),
[deployment_pending_status] bit NOT NULL DEFAULT ((0)),
CONSTRAINT [PK_gobject] PRIMARY KEY ([gobject_id], [namespace_id], [template_definition_id])
);
GO
ALTER TABLE [gobject] ADD FOREIGN KEY ([template_definition_id]) REFERENCES [template_definition] ([template_definition_id]);
GO

View File

@@ -0,0 +1,11 @@
-- Table: gobject_asset_order
CREATE TABLE [gobject_asset_order] (
[gobject_id] int NOT NULL,
[relative_index] float(53,) NOT NULL,
CONSTRAINT [PK_gobject_asset_order] PRIMARY KEY ([gobject_id])
);
GO
ALTER TABLE [gobject_asset_order] ADD FOREIGN KEY ([gobject_id]) REFERENCES [gobject] ([gobject_id]);
GO

View File

@@ -0,0 +1,16 @@
-- Table: gobject_change_log
CREATE TABLE [gobject_change_log] (
[gobject_change_log_id] int NOT NULL,
[gobject_id] int NOT NULL,
[change_date] datetime NULL,
[operation_id] smallint NOT NULL,
[user_comment] nvarchar(1024) NOT NULL DEFAULT (''),
[configuration_version] int NOT NULL DEFAULT ((0)),
[user_profile_name] nvarchar(256) NOT NULL,
CONSTRAINT [PK_gobject_change_log] PRIMARY KEY ([gobject_id], [operation_id])
);
GO
ALTER TABLE [gobject_change_log] ADD FOREIGN KEY ([operation_id]) REFERENCES [lookup_operation] ([operation_id]);
GO

View File

@@ -0,0 +1,11 @@
-- Table: gobject_filter_info_timestamp
CREATE TABLE [gobject_filter_info_timestamp] (
[gobject_id] int NULL,
[timestamp_of_last_change] timestamp NOT NULL,
CONSTRAINT [PK_gobject_filter_info_timestamp] PRIMARY KEY ([timestamp_of_last_change], [gobject_id])
);
GO
ALTER TABLE [gobject_filter_info_timestamp] ADD FOREIGN KEY ([gobject_id]) REFERENCES [gobject] ([gobject_id]);
GO

View File

@@ -0,0 +1,11 @@
-- Table: gobject_friendly_name
CREATE TABLE [gobject_friendly_name] (
[gobject_id] int NOT NULL,
[friendly_name] nvarchar(1024) NOT NULL DEFAULT (''),
CONSTRAINT [PK_gobject_friendly_name] PRIMARY KEY ([gobject_id])
);
GO
ALTER TABLE [gobject_friendly_name] ADD FOREIGN KEY ([gobject_id]) REFERENCES [gobject] ([gobject_id]);
GO

View File

@@ -0,0 +1,7 @@
-- Table: gobject_log_details
CREATE TABLE [gobject_log_details] (
[gobject_id] int NOT NULL,
[tag_name] nvarchar(329) NOT NULL
);
GO

View File

@@ -0,0 +1,6 @@
-- Table: gobject_protected
CREATE TABLE [gobject_protected] (
[gobject_id] int NOT NULL
);
GO

View File

@@ -0,0 +1,13 @@
-- Table: instance
CREATE TABLE [instance] (
[gobject_id] int NOT NULL,
[mx_platform_id] smallint NOT NULL DEFAULT ((0)),
[mx_engine_id] smallint NOT NULL DEFAULT ((0)),
[mx_object_id] smallint NOT NULL DEFAULT ((0)),
CONSTRAINT [PK_instance] PRIMARY KEY ([gobject_id], [gobject_id])
);
GO
ALTER TABLE [instance] ADD FOREIGN KEY ([gobject_id]) REFERENCES [gobject] ([gobject_id]);
GO

View File

@@ -0,0 +1,6 @@
-- Table: intouchviewapptemplate_allsymbols
CREATE TABLE [intouchviewapptemplate_allsymbols] (
[gobject_id] int NOT NULL
);
GO

View File

@@ -0,0 +1,8 @@
-- Table: lookup_category
CREATE TABLE [lookup_category] (
[category_id] smallint NOT NULL,
[category_name] nvarchar(50) NOT NULL,
CONSTRAINT [PK_lookup_category] PRIMARY KEY ([category_id])
);
GO

View File

@@ -0,0 +1,7 @@
-- Table: lookup_folder
CREATE TABLE [lookup_folder] (
[folder_type] smallint NOT NULL,
[folder_type_name] nvarchar(32) NULL
);
GO

View File

@@ -0,0 +1,9 @@
-- Table: lookup_operation
CREATE TABLE [lookup_operation] (
[operation_id] smallint NOT NULL,
[operation_code] nvarchar(50) NOT NULL,
[operation_name] nvarchar(256) NOT NULL,
CONSTRAINT [PK_lookup_operation] PRIMARY KEY ([operation_id])
);
GO

View File

@@ -0,0 +1,8 @@
-- Table: lookup_package_op_status
CREATE TABLE [lookup_package_op_status] (
[status_id] int NOT NULL,
[status_name] nvarchar(50) NOT NULL,
CONSTRAINT [PK_lookup_package_op_status] PRIMARY KEY ([status_id])
);
GO

View File

@@ -0,0 +1,8 @@
-- Table: lookup_status
CREATE TABLE [lookup_status] (
[status_id] int NOT NULL,
[status_name] nvarchar(50) NOT NULL,
CONSTRAINT [PK_lookup_status] PRIMARY KEY ([status_id])
);
GO

View File

@@ -0,0 +1,7 @@
-- Table: lookup_table_name
CREATE TABLE [lookup_table_name] (
[table_id] smallint NOT NULL,
[table_name] nvarchar(250) NULL
);
GO

View File

@@ -0,0 +1,8 @@
-- Table: namespace
CREATE TABLE [namespace] (
[namespace_id] smallint NOT NULL,
[namespace_name] nvarchar(32) NULL,
CONSTRAINT [PK_namespace] PRIMARY KEY ([namespace_id])
);
GO

View File

@@ -0,0 +1,9 @@
-- Table: object_device_linkage
CREATE TABLE [object_device_linkage] (
[gobject_id] int NOT NULL,
[dio_id] int NOT NULL,
[sg_mx_primitive_id] smallint NOT NULL,
CONSTRAINT [PK_object_device_linkage] PRIMARY KEY ([gobject_id])
);
GO

View File

@@ -0,0 +1,9 @@
-- Table: object_wizard_overview_symbols
CREATE TABLE [object_wizard_overview_symbols] (
[gobject_id] int NOT NULL,
[visual_element_id] int NOT NULL,
[change_type] int NOT NULL,
[mx_primitive_id] int NULL
);
GO

View File

@@ -0,0 +1,12 @@
-- Table: object_wizard_symbol_override
CREATE TABLE [object_wizard_symbol_override] (
[gobject_id] int NOT NULL,
[package_id] int NOT NULL,
[symbol_overrides] image(2147483647) NULL,
CONSTRAINT [PK_object_wizard_symbol_override] PRIMARY KEY ([gobject_id], [package_id], [gobject_id], [package_id])
);
GO
ALTER TABLE [object_wizard_symbol_override] ADD FOREIGN KEY ([package_id]) REFERENCES [package] ([package_id]);
GO

View File

@@ -0,0 +1,15 @@
-- Table: object_wizard_symbol_override_mapping
CREATE TABLE [object_wizard_symbol_override_mapping] (
[gobject_id] int NOT NULL,
[package_id] int NOT NULL,
[mx_primitive_id] smallint NOT NULL,
[has_local_overrides] bit NOT NULL,
[thumbnail] image(2147483647) NULL,
[preview] image(2147483647) NULL,
CONSTRAINT [PK_object_wizard_symbol_override_mapping] PRIMARY KEY ([gobject_id], [package_id], [mx_primitive_id], [gobject_id], [mx_primitive_id], [package_id], [gobject_id], [mx_primitive_id], [package_id], [gobject_id], [mx_primitive_id], [package_id])
);
GO
ALTER TABLE [object_wizard_symbol_override_mapping] ADD FOREIGN KEY ([package_id]) REFERENCES [primitive_instance] ([package_id]);
GO

View File

@@ -0,0 +1,9 @@
-- Table: old_checked_in_packages
CREATE TABLE [old_checked_in_packages] (
[package_id] int NULL,
[gobject_id] int NULL,
[is_template] bit NULL,
[is_being_referenced] bit NULL DEFAULT ((0))
);
GO

View File

@@ -0,0 +1,13 @@
-- Table: operation
CREATE TABLE [operation] (
[operation_id] int NOT NULL,
[user_profile_id] int NOT NULL,
[operation_name] nvarchar(300) NOT NULL,
[start_time] datetime NOT NULL DEFAULT (getdate()),
CONSTRAINT [PK_operation] PRIMARY KEY ([operation_id], [user_profile_id])
);
GO
ALTER TABLE [operation] ADD FOREIGN KEY ([user_profile_id]) REFERENCES [user_profile] ([user_profile_id]);
GO

View File

@@ -0,0 +1,13 @@
-- Table: operation_message
CREATE TABLE [operation_message] (
[message_id] int NOT NULL,
[operation_id] int NOT NULL,
[message_text] nvarchar(300) NOT NULL,
[message_time] datetime NOT NULL DEFAULT (getdate()),
CONSTRAINT [PK_operation_message] PRIMARY KEY ([message_id], [operation_id])
);
GO
ALTER TABLE [operation_message] ADD FOREIGN KEY ([operation_id]) REFERENCES [operation] ([operation_id]);
GO

View File

@@ -0,0 +1,11 @@
-- Table: operation_status
CREATE TABLE [operation_status] (
[operation_id] int NOT NULL,
[status] int NOT NULL,
CONSTRAINT [PK_operation_status] PRIMARY KEY ([operation_id], [status], [operation_id])
);
GO
ALTER TABLE [operation_status] ADD FOREIGN KEY ([operation_id]) REFERENCES [operation] ([operation_id]);
GO

View File

@@ -0,0 +1,8 @@
-- Table: operation_status_look_up
CREATE TABLE [operation_status_look_up] (
[status] int NOT NULL,
[status_name] varchar(100) NOT NULL,
CONSTRAINT [PK_operation_status_look_up] PRIMARY KEY ([status])
);
GO

View File

@@ -0,0 +1,16 @@
-- Table: ow_group_def
CREATE TABLE [ow_group_def] (
[gobject_id] int NOT NULL,
[package_id] int NOT NULL,
[ow_group_id] int NOT NULL,
[prompt] nvarchar(260) NOT NULL,
[GUID] varchar(36) NOT NULL,
[description] nvarchar(260) NULL,
[visibility_rules] nvarchar(-1) NULL,
CONSTRAINT [PK_ow_group_def] PRIMARY KEY ([gobject_id], [package_id], [ow_group_id], [ow_group_id])
);
GO
ALTER TABLE [ow_group_def] ADD FOREIGN KEY ([ow_group_id]) REFERENCES [ow_group_id] ([ow_group_id]);
GO

View File

@@ -0,0 +1,7 @@
-- Table: ow_group_id
CREATE TABLE [ow_group_id] (
[ow_group_id] int NOT NULL,
CONSTRAINT [PK_ow_group_id] PRIMARY KEY ([ow_group_id])
);
GO

View File

@@ -0,0 +1,13 @@
-- Table: ow_group_override
CREATE TABLE [ow_group_override] (
[gobject_id] int NOT NULL,
[package_id] int NOT NULL,
[ow_group_id] int NOT NULL,
[property_bitmask] int NOT NULL,
CONSTRAINT [PK_ow_group_override] PRIMARY KEY ([gobject_id], [package_id], [ow_group_id], [ow_group_id])
);
GO
ALTER TABLE [ow_group_override] ADD FOREIGN KEY ([ow_group_id]) REFERENCES [ow_group_id] ([ow_group_id]);
GO

View File

@@ -0,0 +1,17 @@
-- Table: ow_instance_setting
CREATE TABLE [ow_instance_setting] (
[gobject_id] int NOT NULL,
[package_id] int NOT NULL,
[name] nvarchar(329) NOT NULL,
[ow_setting_type] int NOT NULL,
[is_dmv] bit NOT NULL,
[override_value] nvarchar(-1) NULL,
[is_mx] bit NOT NULL,
[is_implicit] bit NULL,
CONSTRAINT [PK_ow_instance_setting] PRIMARY KEY ([gobject_id], [package_id], [name], [ow_setting_type], [is_dmv], [ow_setting_type])
);
GO
ALTER TABLE [ow_instance_setting] ADD FOREIGN KEY ([ow_setting_type]) REFERENCES [ow_lu_setting] ([ow_setting_type]);
GO

View File

@@ -0,0 +1,19 @@
-- Table: ow_link_def
CREATE TABLE [ow_link_def] (
[gobject_id] int NOT NULL,
[package_id] int NOT NULL,
[ow_link_id] int NOT NULL,
[ow_link_type] int NOT NULL,
[name] nvarchar(329) NOT NULL,
[mx_primitive_id] smallint NULL,
[dynamic_attribute_id] bigint NULL,
[optional_id] int NULL,
[property_bitmask] int NOT NULL,
[sort] int NOT NULL,
CONSTRAINT [PK_ow_link_def] PRIMARY KEY ([gobject_id], [package_id], [ow_link_id], [ow_link_id])
);
GO
ALTER TABLE [ow_link_def] ADD FOREIGN KEY ([ow_link_id]) REFERENCES [ow_link_id] ([ow_link_id]);
GO

View File

@@ -0,0 +1,11 @@
-- Table: ow_link_id
CREATE TABLE [ow_link_id] (
[ow_link_id] int NOT NULL,
[ow_opt_or_choice_id] int NOT NULL,
CONSTRAINT [PK_ow_link_id] PRIMARY KEY ([ow_link_id], [ow_opt_or_choice_id])
);
GO
ALTER TABLE [ow_link_id] ADD FOREIGN KEY ([ow_opt_or_choice_id]) REFERENCES [ow_opt_or_choice_id] ([ow_opt_or_choice_id]);
GO

View File

@@ -0,0 +1,17 @@
-- Table: ow_lu_definition
CREATE TABLE [ow_lu_definition] (
[ow_setting_type] int NOT NULL,
[primitive_definition_id] int NOT NULL,
[template_definition_id] int NOT NULL,
[boolean_analog] bit NOT NULL,
[ext] varchar(20) NOT NULL,
[link_setting] int NULL,
[link_feature] int NULL,
[mx_attribute_id] smallint NULL,
CONSTRAINT [PK_ow_lu_definition] PRIMARY KEY ([ow_setting_type], [primitive_definition_id], [ow_setting_type])
);
GO
ALTER TABLE [ow_lu_definition] ADD FOREIGN KEY ([ow_setting_type]) REFERENCES [ow_lu_setting] ([ow_setting_type]);
GO

View File

@@ -0,0 +1,18 @@
-- Table: ow_lu_setting
CREATE TABLE [ow_lu_setting] (
[ow_setting_type] int NOT NULL,
[category] varchar(10) NOT NULL,
[mx_data_type] tinyint NULL,
[setting_name] nvarchar(30) NOT NULL,
[feature_id] int NULL,
[feature_sub_id] int NULL,
[parent_type] int NULL,
[no_ext] bit NULL,
[raw_value] nvarchar(100) NULL,
CONSTRAINT [PK_ow_lu_setting] PRIMARY KEY ([ow_setting_type], [mx_data_type])
);
GO
ALTER TABLE [ow_lu_setting] ADD FOREIGN KEY ([mx_data_type]) REFERENCES [data_type] ([mx_data_type]);
GO

View File

@@ -0,0 +1,21 @@
-- Table: ow_opt_or_choice_def
CREATE TABLE [ow_opt_or_choice_def] (
[gobject_id] int NOT NULL,
[package_id] int NOT NULL,
[ow_opt_or_choice_id] int NOT NULL,
[prompt] nvarchar(260) NOT NULL,
[GUID] varchar(36) NOT NULL,
[name] varchar(10) NOT NULL,
[description] nvarchar(260) NULL,
[choice_sequence_number] int NULL,
[after_group_or_option] int NULL,
[initial_value] bit NOT NULL DEFAULT ((0)),
[visibility_rules] nvarchar(-1) NULL,
[sort] int NOT NULL,
CONSTRAINT [PK_ow_opt_or_choice_def] PRIMARY KEY ([gobject_id], [package_id], [ow_opt_or_choice_id], [ow_opt_or_choice_id])
);
GO
ALTER TABLE [ow_opt_or_choice_def] ADD FOREIGN KEY ([ow_opt_or_choice_id]) REFERENCES [ow_opt_or_choice_id] ([ow_opt_or_choice_id]);
GO

View File

@@ -0,0 +1,11 @@
-- Table: ow_opt_or_choice_id
CREATE TABLE [ow_opt_or_choice_id] (
[ow_opt_or_choice_id] int NOT NULL,
[ow_group_id] int NOT NULL,
CONSTRAINT [PK_ow_opt_or_choice_id] PRIMARY KEY ([ow_opt_or_choice_id], [ow_group_id])
);
GO
ALTER TABLE [ow_opt_or_choice_id] ADD FOREIGN KEY ([ow_group_id]) REFERENCES [ow_group_id] ([ow_group_id]);
GO

View File

@@ -0,0 +1,14 @@
-- Table: ow_opt_or_choice_override
CREATE TABLE [ow_opt_or_choice_override] (
[gobject_id] int NOT NULL,
[package_id] int NOT NULL,
[ow_opt_or_choice_id] int NOT NULL,
[property_bitmask] int NULL,
[override_value] bit NULL,
CONSTRAINT [PK_ow_opt_or_choice_override] PRIMARY KEY ([gobject_id], [package_id], [ow_opt_or_choice_id], [ow_opt_or_choice_id])
);
GO
ALTER TABLE [ow_opt_or_choice_override] ADD FOREIGN KEY ([ow_opt_or_choice_id]) REFERENCES [ow_opt_or_choice_id] ([ow_opt_or_choice_id]);
GO

View File

@@ -0,0 +1,18 @@
-- Table: ow_setting_def
CREATE TABLE [ow_setting_def] (
[gobject_id] int NOT NULL,
[package_id] int NOT NULL,
[ow_setting_id] int NOT NULL,
[ow_setting_type] int NOT NULL,
[reference] nvarchar(260) NULL,
[property_id] varchar(36) NULL,
[initial_value] nvarchar(-1) NULL,
[property_bitmask] int NULL,
[sort] int NOT NULL,
CONSTRAINT [PK_ow_setting_def] PRIMARY KEY ([gobject_id], [package_id], [ow_setting_id], [ow_setting_type], [ow_setting_id])
);
GO
ALTER TABLE [ow_setting_def] ADD FOREIGN KEY ([ow_setting_id]) REFERENCES [ow_setting_id] ([ow_setting_id]);
GO

View File

@@ -0,0 +1,11 @@
-- Table: ow_setting_id
CREATE TABLE [ow_setting_id] (
[ow_setting_id] int NOT NULL,
[ow_link_id] int NOT NULL,
CONSTRAINT [PK_ow_setting_id] PRIMARY KEY ([ow_setting_id], [ow_link_id])
);
GO
ALTER TABLE [ow_setting_id] ADD FOREIGN KEY ([ow_link_id]) REFERENCES [ow_link_id] ([ow_link_id]);
GO

View File

@@ -0,0 +1,14 @@
-- Table: ow_setting_override
CREATE TABLE [ow_setting_override] (
[gobject_id] int NOT NULL,
[package_id] int NOT NULL,
[ow_setting_id] int NOT NULL,
[override_value] nvarchar(-1) NULL,
[property_bitmask] int NULL,
CONSTRAINT [PK_ow_setting_override] PRIMARY KEY ([gobject_id], [package_id], [ow_setting_id], [ow_setting_id])
);
GO
ALTER TABLE [ow_setting_override] ADD FOREIGN KEY ([ow_setting_id]) REFERENCES [ow_setting_id] ([ow_setting_id]);
GO

View File

@@ -0,0 +1,17 @@
-- Table: ow_symbol_setting
CREATE TABLE [ow_symbol_setting] (
[gobject_id] int NOT NULL,
[package_id] int NOT NULL,
[sym_name] nvarchar(33) NOT NULL,
[ow_setting_type] int NOT NULL,
[reference] nvarchar(260) NOT NULL,
[property_id] nvarchar(36) NULL,
[value] nvarchar(-1) NULL,
[property_bitmask] int NULL,
CONSTRAINT [PK_ow_symbol_setting] PRIMARY KEY ([gobject_id], [package_id], [sym_name], [ow_setting_type], [reference], [ow_setting_type])
);
GO
ALTER TABLE [ow_symbol_setting] ADD FOREIGN KEY ([ow_setting_type]) REFERENCES [ow_lu_setting] ([ow_setting_type]);
GO

View File

@@ -0,0 +1,21 @@
-- Table: owned_visual_element
CREATE TABLE [owned_visual_element] (
[gobject_id] int NOT NULL,
[package_id] int NOT NULL,
[mx_primitive_id] smallint NOT NULL,
[visual_element_id] int NOT NULL,
[thumbnail] image(2147483647) NULL,
[description] nvarchar(1024) NULL,
[visual_element_definition] image(2147483647) NOT NULL,
[is_thumbnail_dirty] bit NOT NULL DEFAULT ((0)),
[visual_element_definition_grm] image(2147483647) NOT NULL,
[content_type] nvarchar(1024) NULL,
[visual_element_crossRef] nvarchar(-1) NULL,
[preview] image(2147483647) NULL,
CONSTRAINT [PK_owned_visual_element] PRIMARY KEY ([gobject_id], [package_id], [mx_primitive_id], [gobject_id], [mx_primitive_id], [package_id], [gobject_id], [mx_primitive_id], [package_id], [gobject_id], [mx_primitive_id], [package_id])
);
GO
ALTER TABLE [owned_visual_element] ADD FOREIGN KEY ([package_id]) REFERENCES [visual_element_version] ([package_id]);
GO

Some files were not shown because too many files have changed in this diff Show More