Feed aggregator
Will Oracle AI Database be renamed again to Oracle SI Database?
how to find lock blockers and lock waiters in RAC
DBMS_DST.get_latest_timezone_version
How can I set JOB_CREATOR to the job owner when creating a Scheduler job in another schema?
Application Root uninstall issues
Reg career path
Calling Oracle Apex from Oracle Forms without User ID and Password Authentication
Moving data from one partition to another partition of the same table -Best methods
Oracle Forensics - Can we Understand what Happened?
Posted by Pete On 06/10/26 At 02:16 PM
Generate a PL/SQL Encryption and Decryption Package, Again
Posted by Pete On 28/09/26 At 08:36 AM
Extreme PL/SQL - Creating a simple programming language interpreter in PL/SQL
Posted by Pete On 23/09/26 At 07:52 AM
Extreme PL/SQL - Running an Assembly Language Program in PL/SQL
Posted by Pete On 16/09/26 At 08:54 AM
Oracle Security AnythingLLM Tools
Posted by Pete On 10/09/26 At 10:35 AM
Perform a Security Audit of an Old Oracle Database
Posted by Pete On 09/09/26 At 12:33 PM
Can Synonyms Point to Synonyms?
Posted by Pete On 07/09/26 At 08:08 AM
Contexts are Database Level Objects in Oracle
Posted by Pete On 02/09/26 At 09:30 AM
GoldenGate 26ai AutoSchema TYPEMAP: overriding the default datatype mapping
In my blog about the new automatic schema evolution feature in GoldenGate 26ai, I was replicating the following table from Oracle to PostgreSQL:
CREATE TABLE autosch.t1 (
id NUMBER PRIMARY KEY,
name VARCHAR2(50),
amount NUMBER(10,2),
created DATE DEFAULT SYSDATE,
note VARCHAR2(20) DEFAULT 'added'
);
The replicat created a PostgreSQL table from an Oracle one during an initial load. It worked, but one choice was not mine: the NUMBER primary key became a varchar(50). Automatic Schema Evolution takes its datatypes from a default mapping, and the TYPEMAP option is how you override it.
Automatic schema evolution is a preview feature in GoldenGate 26ai: its syntax, messages and behavior can change before it becomes generally available, so the results below apply to the 23.26.3 release I tested.
Where the default mapping comes fromEach GoldenGate 26ai home ships the default mappings as one JSON file per source database and per target database. They are in $OGG_HOME/lib/utl/autoschema/MappingJSON, where MappingGenerator.py combines a source and a target file. For this example, the files are AutoSchemaOracleSourceMappings.json and AutoSchemaPostgreSQLTargetMappings.json.
Reading the two JSON files for an Oracle source and a PostgreSQL target, the default mappings that matter for the example table are:
Oracle source typePostgreSQL target typenumber(precision,scale)decimal(precision,scale)numbervarchar(precision), if the source data length is at most 10485760numbertext, if the value is still too large to be accommodateddatetimestamp(precision)
A NUMBER without precision maps to varchar, and a NUMBER(p,s) maps to decimal(p,s). This is why the key of my source table did not become a number.
For each test below, I drop autosch.t1 on PostgreSQL and start a new replicat, always named REPTM, on the same file.
AUTOSCHEMAOPTIONS TYPEMAP must be written on a separate line, next to DDL AUTOSCHEMA INCLUDE ALL. The source type and the target type are separated by an equal sign. The option maps every occurrence of the source type to the given target type. Both types must be enclosed in single quotes, otherwise the replicat abends with OGG-30694:
REPLICAT REPTM
TARGETDB ogg_pg_01 USERIDALIAS pgalias DOMAIN OracleGoldenGate
DDL AUTOSCHEMA INCLUDE ALL
AUTOSCHEMAOPTIONS TYPEMAP 'number' = 'bigint'
MAP AUTOSCH.*, TARGET AUTOSCH.*;
The replicat report logs that the override was read:
2026-09-19T15:23:47.078+0000 INFO OGG-30632 Oracle GoldenGate Delivery for PostgreSQL, REPTM.prm: AutoSchemaOptions TypeMap is used, TypeNameMap: number -> bigint.
The table created in PostgreSQL, seen with \d autosch.t1 in psql, now shows a bigint as primary key
ogg_test_pg=# \d autosch.t1
Table "autosch.t1"
Column | Type | Collation | Nullable | Default
---------+--------------------------------+-----------+----------+---------
id | bigint | | not null |
name | character varying(50) | | |
amount | numeric(10,2) | | |
created | timestamp(0) without time zone | | |
note | character varying(20) | | |
Indexes:
"t1_pkey" PRIMARY KEY, btree (id)
The amount column type did not change, since 'number' matches the source type NUMBER without precision. NUMBER(10,2) is another source type, and is not affected by the TYPEMAP instruction.
AUTOSCHEMAOPTIONS
When mapping multiple types, each mapping needs a dedicated AUTOSCHEMAOPTIONS line:
DDL AUTOSCHEMA INCLUDE ALL
AUTOSCHEMAOPTIONS TYPEMAP 'number' = 'bigint'
AUTOSCHEMAOPTIONS TYPEMAP 'date' = 'date'
AUTOSCHEMAOPTIONS TYPEMAP 'number(10,2)' = 'real'
Every entry is logged separately:
2026-09-19T16:29:07.403+0000 INFO OGG-30632 Oracle GoldenGate Delivery for PostgreSQL, REPTM.prm: AutoSchemaOptions TypeMap is used, TypeNameMap: date -> date.
2026-09-19T16:29:07.403+0000 INFO OGG-30632 Oracle GoldenGate Delivery for PostgreSQL, REPTM.prm: AutoSchemaOptions TypeMap is used, TypeNameMap: number -> bigint.
2026-09-19T16:29:07.403+0000 INFO OGG-30632 Oracle GoldenGate Delivery for PostgreSQL, REPTM.prm: AutoSchemaOptions TypeMap is used, TypeNameMap: number\(10,2\) -> real.
And this is the resulting table:
Column | Type | Collation | Nullable | Default
---------+-----------------------+-----------+----------+---------
id | bigint | | not null |
name | character varying(50) | | |
amount | real | | |
created | date | | |
note | character varying(20) | | |
All three overrides are applied. An interesting feature is that the replicat logs an OGG-03056 warning about the target column being smaller than the source column:
2026-09-19T16:29:07.419+0000 WARNING OGG-03056 Oracle GoldenGate Delivery for PostgreSQL, REPTM.prm: Source table AUTOSCH.T1 column CREATED data size exceeds the maximum target table autosch.t1 column created size. Automatic truncation is enabled for all tables/columns without further warnings.
As expected, mapping DATE to a PostgreSQL date loses the time part, and it can be seen in the target table:
id | name | amount | created | note
----+-------+--------+------------+-------
1 | row 1 | 10.25 | 2026-09-19 | added
2 | row 2 | 20.5 | 2026-09-19 | added
An Oracle DATE carries a time, so the default timestamp(0) mapping is the right one.
AUTOSCHEMAOPTIONS at the MAP level
The same option can be set on a MAP statement, between parentheses. It then only applies to the tables of that MAP statement instead of the whole replicat:
MAP AUTOSCH.*, TARGET AUTOSCH.*, AUTOSCHEMAOPTIONS (TYPEMAP 'number' = 'bigint');
2026-09-19T16:29:33.344+0000 INFO OGG-06506 Oracle GoldenGate Delivery for PostgreSQL, REPTM.prm: Wildcard MAP resolved (entry AUTOSCH.*): MAP "AUTOSCH"."T1", TARGET AUTOSCH."T1", AUTOSCHEMAOPTIONS (TYPEMAP 'number' = 'bigint').
2026-09-19T16:29:33.344+0000 INFO OGG-30632 Oracle GoldenGate Delivery for PostgreSQL, REPTM.prm: AutoSchemaOptions TypeMap is used, TypeNameMap: number -> bigint.
The table has a bigint key again. In this case, the other number columns outside of the MAP statement would keep the default varchar(50) mapping.
To change a single column, write the column name on the left side of the equal sign, instead of specifying the source type. The two forms work, first with the full name of the column:
AUTOSCHEMAOPTIONS TYPEMAP AUTOSCH.T1.NAME = 'text'
And at the MAP level, with the column name alone:
MAP AUTOSCH.T1, TARGET AUTOSCH.T1, AUTOSCHEMAOPTIONS (TYPEMAP NAME = 'text');
Both give the same table, with only name changed into text:
Column | Type | Collation | Nullable | Default
---------+--------------------------------+-----------+----------+---------
id | character varying(50) | | not null |
name | text | | |
amount | numeric(10,2) | | |
created | timestamp(0) without time zone | | |
note | character varying(20) | | |
To remap several columns on the same MAP, separate the mappings with commas inside the parentheses, and write TYPEMAP only once:
MAP AUTOSCH.T1, TARGET AUTOSCH.T1, AUTOSCHEMAOPTIONS (TYPEMAP NAME = 'text', NOTE = 'text');
Column | Type | Collation | Nullable | Default
---------+--------------------------------+-----------+----------+---------
id | character varying(50) | | not null |
name | text | | |
amount | numeric(10,2) | | |
created | timestamp(0) without time zone | | |
note | text | | |
A type and a column can be mixed in the same list. For example, AUTOSCHEMAOPTIONS (TYPEMAP 'number' = 'bigint', NAME = 'text') gives a bigint key and a text name.
- The default mapping files are located in
$OGG_HOME/lib/utl/autoschema/MappingJSON. Check them before letting a replicat create tables. ANUMBERwithout precision becomes avarcharon PostgreSQL, for instance. - The override syntax is
AUTOSCHEMAOPTIONS TYPEMAP 'source_type' = 'target_type'. Use an equal sign, both types in single quotes, and one mapping per line. - A source type is matched as written. An override on
'number'does not change aNUMBER(10,2)column, which needs a separate'number(10,2)'entry. - On a
MAP, put the option between parentheses to limit it to those tables. For one column, useAUTOSCH.T1.NAME = 'text'globally orNAME = 'text'on theMAP. - Pay attention to
OGG-03056warnings: a target type smaller than the source, likeDATEtodate, truncates the data without stopping the initial load.
L’article GoldenGate 26ai AutoSchema TYPEMAP: overriding the default datatype mapping est apparu en premier sur dbi Blog.
Archiving 5 TB from Oracle into SQL Server: views, indexes and compression (part 2)
At the end of part 1 the data was across, at 42,170 rows per second into a database that could not answer a single question. Every nonclustered index disabled, the largest tables loaded as heaps with no primary key, every foreign key switched off, and 4,784 views that mostly did not compile.
This part is about putting it back: translating the views, rebuilding the keys and indexes under a Standard Edition licence that makes every build offline, and compressing 3.2 TB of heaps that turned out to be mostly empty space.
The logbook resumes at day 5.
Day 5. Four thousand views to convert4,658 of the 4,784 views live in a single schema, and nobody needed all of them. We went through the list with the client first and kept the ones that actually matter to the business, which brought the scope down to about 710. SSMA converted around 600 of those on its own.
SSMA fails mostly on CONNECT BY, START WITH and SYS_CONNECT_BY_PATH. For some easy cases, DECODE to CASE and NVL handles it well, for the record. For more complex use case, we needed to find an automatic solution process. So first, we discussed with the client to know which view were critical and needed to be migrated. Then, with this list, we leveraged the power of AI to help us converting the remaining views based on the following workflow:
The workflow
SSMA converts what it can. The failures get extracted from dba_views into files. Then, and this is the step that changes the economics, they get classified by pattern in the Oracle source rather than one at a time by error message.
Translation itself was done in batches of one family at a time, outside production, on extracted files, with an AI assistant and a context file describing the type mapping decisions, the column types of the referenced tables and the target version. Then human review, tests against Oracle and finally, deployment.
The catalogue of families is the real deliverable. Eight of them closed the great majority of the cases:
FamilyVerdict+ concatenation on numerics (col + '_' + col)CONCATUntyped NULL in a UNION, cast to numeric(38,10) against a datetime2 siblingretype the castCall to a package function that was never migratedinline it if possiblessma_oracle.* helper functionsinstall the extension packXML extraction (.extract().getStringVal())rewrite part of the queryTO_TIMESTAMP_TZrewrite part of the queryDate arithmetic landing in floatredefine type mappingCONNECT BY used as a generatormaterialise the level connectors
And a word on the AI, since that is the part people ask about. No data ever left the environment: only the view definitions were processed, and every test against the live database was run by hand so we kept control of the process. We could have added MCP connectors and let a local model run the tests itself, but with the number of days allocated to the project and the small number of views left to fix, doing it manually was simpler. Around 85 percent of the views wanted by the client had already come through SSMA.
Day 6. Putting the indexes back and making the database smaller Recovering what I had thrown awayThe primary keys I dropped to load into heaps had left no trace on the SQL Server side. Nothing in sys.indexes, no disabled definition, nothing.
Index builds are offline and single-threaded. The table is locked for the duration, metadata reads queue behind it, but we managed to run 4 builds in parallel. SORT_IN_TEMPDB = OFF for the clustered builds. The sort covers the entire table, and 36 GB of tempdb was never going to hold a 900 GB sort.
Meanwhile percent_complete sits at 0 for the whole build, with the serene confidence of something that has never been asked to account for itself. It works for BACKUP DATABASE. It does not work here. Real progress lives in sys.dm_exec_query_profiles, operator by operator :
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT physical_operator_name, row_count, estimate_row_count,
CAST(row_count*100.0/NULLIF(estimate_row_count,0) AS decimal(5,1)) AS pct
FROM sys.dm_exec_query_profiles WITH (NOLOCK)
WHERE session_id = <spid of the build>
ORDER BY node_id;
Those are not three parallel counters. They are three sequential phases of one pipeline, and only one of them moves at a time. The scan reads the source table, and while it runs its percentage is the only honest progress figure available. The sort is a blocking operator: it cannot emit a single row until it has consumed all 31.9 million, so it stays at zero through the whole scan and then through the whole sort. That is the blind spot. Once the scan reaches 100 percent you get a long stretch where all three counters report nothing while the server works flat out, and if the sort spills to disk that stretch can outlast the other two phases combined. Index Insert starts moving only when the sort begins handing rows over, and it is the first number that means the end is actually in sight.
Why compress, and what it returnedOn a read-only database, PAGE compression has no recurring write cost. You keep the read benefit and pay nothing back. The argument is rarely this clean.
Compression trades CPU for I/O, and on an archive that trade is one-sided. These pages are read rarely, and when they are, nothing heavy is competing for the processor, so the decompression cost lands on a machine with nothing better to do. What you get back is density: more rows per page, which means fewer reads to find the one row somebody asked for and more of the table sitting in memory at once.
The data was unusually well suited to it, for a reason worth spelling out. These tables carry roughly 200 numeric columns, mostly null, plus five nvarchar(200) aggregation labels that repeat endlessly. Here is the part that surprises people: a fixed-length numeric column that is NULL still occupies its full width in SQL Server. “Mostly null” saves nothing before compression. It is precisely why the heap was that large, and precisely what the page dictionary destroys.
The allocated space per row tells the story:
TableRowsGB (heap)bytes allocated per rowTABLE1251,777,518960.5~4,100TABLE2229,881,717876.9~4,100TABLE3343,997,550874.8~2,700TABLE4447,972,849227.9~550TABLE5519,727,931120.2~250TABLE6505,819,544117.0~250Three tables hold 2.7 TB of the 3.2 TB total. At around 4 KB of allocated space per row against an 8 KB page, the top two fit around two rows per page and waste most of what is left.
Then the first build, on 505,819,544 rows with a five-column primary key:
ALTER TABLE <TABLE6>
ADD CONSTRAINT PK_TABLE6 PRIMARY KEY CLUSTERED
(<COLUMNS_NAME>)
WITH (DATA_COMPRESSION = PAGE, SORT_IN_TEMPDB = OFF);
117 GB to 30.7 GB. A ratio of 3.81.
Finally, as you can see above, more than 2.5 T have been reclaimed (datafiles were full with the heaps). Compression has made huge benefits and that means less data on disks, which means fewer pages to take in the buffer (less IO).
The script that puts everything backAcross every schema that is two cursors, one rebuilding the disabled indexes with compression and one re-enabling the foreign keys. It is driven by the same #Schemas table as the disable script in part 1:
SET NOCOUNT ON;
DECLARE @Execute bit = 0; -- 0 = preview ; 1 = execute
DECLARE @cmd nvarchar(max);
DECLARE @n int = 0;
DECLARE reb_cur CURSOR LOCAL FAST_FORWARD FOR
SELECT N'ALTER INDEX ' + QUOTENAME(i.name)
+ N' ON ' + QUOTENAME(s.name) + N'.' + QUOTENAME(t.name)
+ N' REBUILD WITH (DATA_COMPRESSION = PAGE);'
FROM sys.indexes i
JOIN sys.tables t ON i.object_id = t.object_id
JOIN sys.schemas s ON t.schema_id = s.schema_id
WHERE s.name IN (SELECT schema_name FROM #Schemas)
AND i.is_disabled = 1
ORDER BY s.name, t.name, i.name;
OPEN reb_cur;
FETCH NEXT FROM reb_cur INTO @cmd;
WHILE @@FETCH_STATUS = 0
BEGIN
SET @n += 1;
PRINT @cmd;
IF @Execute = 1
BEGIN
BEGIN TRY EXEC sys.sp_executesql @cmd; END TRY
BEGIN CATCH PRINT N' -- FAILED: ' + ERROR_MESSAGE(); END CATCH
END
FETCH NEXT FROM reb_cur INTO @cmd;
END
CLOSE reb_cur;
DEALLOCATE reb_cur;
PRINT N'---';
PRINT CONVERT(nvarchar(10), @n) + N' index '
+ CASE WHEN @Execute = 1 THEN N'rebuilt.' ELSE N'to be rebuilt (preview).' END;
GO
SET NOCOUNT ON;
DECLARE @Execute bit = 1; -- 0 = preview ; 1 = executer
DECLARE @cmd nvarchar(max);
DECLARE @n int = 0;
DECLARE fkc_cur CURSOR LOCAL FAST_FORWARD FOR
SELECT N'ALTER TABLE ' + QUOTENAME(s.name) + N'.' + QUOTENAME(t.name)
+ N' WITH NOCHECK CHECK CONSTRAINT ' + QUOTENAME(fk.name) + N';'
FROM sys.foreign_keys fk
JOIN sys.tables t ON fk.parent_object_id = t.object_id
JOIN sys.schemas s ON t.schema_id = s.schema_id
WHERE s.name IN (SELECT schema_name FROM #Schemas);
OPEN fkc_cur;
FETCH NEXT FROM fkc_cur INTO @cmd;
WHILE @@FETCH_STATUS = 0
BEGIN
SET @n += 1;
PRINT @cmd;
IF @Execute = 1
BEGIN
BEGIN TRY EXEC sys.sp_executesql @cmd; END TRY
BEGIN CATCH PRINT N' -- FAILED : ' + ERROR_MESSAGE(); END CATCH
END
FETCH NEXT FROM fkc_cur INTO @cmd;
END
CLOSE fkc_cur;
DEALLOCATE fkc_cur;
PRINT N'---';
PRINT CONVERT(nvarchar(10), @n) + N' FK '
+ CASE WHEN @Execute = 1 THEN N'activated and validated.' ELSE N'to be activated (preview).' END;
GO
All 6,072 foreign keys were already back, active and marked not trusted, because the loading script re-enabled them with NOCHECK. Revalidating them with WITH CHECK would mean full joins against the largest tables in the database.
On a read-only archive whose source integrity was enforced by Oracle for fifteen years, that buys one thing: the optimizer regains the right to eliminate redundant joins. Worth having on a schema of deeply nested views, not worth days of scanning here. They stayed not trusted.
Day 7. Testing 600 viewsThe principle is simple: run every view, record what happened, and never lose the record. Each one gets a SELECT TOP (100) * inside a TRY/CATCH, so a view that fails is logged rather than fatal, and the outcome lands in a table with its duration in milliseconds. The cursor only picks up views that are not already recorded, which means an interrupted run resumes where it stopped instead of starting over, and the duration column is what you sort on once it is done:
SET NOCOUNT ON;
IF OBJECT_ID('dbo.ViewTestResults') IS NULL
CREATE TABLE dbo.ViewTestResults (
schema_name sysname,
view_name sysname,
statut varchar(20),
duree_ms int,
message nvarchar(2048),
tested_at datetime2 DEFAULT SYSDATETIME(),
CONSTRAINT PK_ViewTestResults PRIMARY KEY (schema_name, view_name)
);
DECLARE @sch sysname, @vue sysname, @sql nvarchar(max), @t0 datetime2, @ms int, @i int = 0, @n int;
SELECT @n = COUNT(*)
FROM sys.views v JOIN sys.schemas s ON v.schema_id = s.schema_id
WHERE s.name = 'SCDAT'
AND NOT EXISTS (SELECT 1 FROM dbo.ViewTestResults r
WHERE r.schema_name = s.name AND r.view_name = v.name);
RAISERROR('=== %d view remaining ===', 0, 1, @n) WITH NOWAIT;
DECLARE c CURSOR FAST_FORWARD FOR
SELECT s.name, v.name
FROM sys.views v JOIN sys.schemas s ON v.schema_id = s.schema_id
WHERE s.name = 'SCDAT'
AND NOT EXISTS (SELECT 1 FROM dbo.ViewTestResults r
WHERE r.schema_name = s.name AND r.view_name = v.name)
ORDER BY v.name;
OPEN c;
FETCH NEXT FROM c INTO @sch, @vue;
WHILE @@FETCH_STATUS = 0
BEGIN
SET @i += 1;
RAISERROR('[%d/%d] %s.%s', 0, 1, @i, @n, @sch, @vue) WITH NOWAIT;
SET @t0 = SYSDATETIME();
BEGIN TRY
SET @sql = N'SELECT TOP (100) * FROM ' + QUOTENAME(@sch) + N'.' + QUOTENAME(@vue) + N';';
EXEC sp_executesql @sql;
SET @ms = DATEDIFF(MILLISECOND, @t0, SYSDATETIME());
INSERT dbo.ViewTestResults (schema_name, view_name, statut, duree_ms, message)
VALUES (@sch, @vue, 'OK', @ms, NULL);
END TRY
BEGIN CATCH
SET @ms = DATEDIFF(MILLISECOND, @t0, SYSDATETIME());
INSERT dbo.ViewTestResults (schema_name, view_name, statut, duree_ms, message)
VALUES (@sch, @vue, 'FAILED', @ms, ERROR_MESSAGE());
RAISERROR(' -> FAILED: %s', 0, 1, @vue) WITH NOWAIT;
END CATCH
FETCH NEXT FROM c INTO @sch, @vue;
END
CLOSE c; DEALLOCATE c;
RAISERROR('=== Done ===', 0, 1) WITH NOWAIT;
-- summary
SELECT statut, COUNT(*) AS nb, SUM(duree_ms) AS duree_totale_ms
FROM dbo.ViewTestResults GROUP BY statut;
-- failed
SELECT schema_name, view_name, duree_ms, message
FROM dbo.ViewTestResults WHERE statut='FAILED' ORDER BY view_name;
-- all
SELECT schema_name, view_name, statut, duree_ms
FROM dbo.ViewTestResults ORDER BY duree_ms DESC;
The result looked excellent: 604 views OK, zero errors, against real data. Six skipped because they are not implemented to be run without an explicit filter added. SELECT TOP 100 * without a filter is not a representative test but still confirms that definition and joins are working fine. So: test with the filter the users will actually write. A view that only misbehaves when queried in a way nobody queries it is not a broken view, but you cannot know that from a green tick.
Database set to read-only, final backup taken and user’s specific queries tested with success, the story now comes to a happy end.
What I would tell the next personAn archive migration is not a cheap application migration. It is a different decision set, and read-only is the input that drives most of it. 25,000 triggers are pointless, PAGE compression free, and untrusted foreign keys perfectly acceptable. It even tells you how to index: broadly and in advance, because nobody can tell you which row an auditor will want in three years. Before anything else, ask what the target will be used for. The answer rewrites the plan before the first byte moves.
Measure, and distrust anything that presents itself as progress. The SSMA counter refreshes per batch and sits still for minutes at a time. percent_complete stays at zero for an entire index build. A green tick on SELECT TOP 100 confirms that the joins resolve and nothing beyond that. In every one of those cases the real number was sitting in a DMV.
On views, the conversion rate is the least useful number in the report. What made four thousand views a finite piece of work was not the translation but the two steps around it: agreeing the real scope with the client first, then classifying what remained by pattern in the Oracle source rather than one error message at a time. An AI assistant translates a family of twenty views very well. It validates nothing, and the test against the source stays yours.
Compression on an archive is the rare decision with nothing to weigh against it. 117 GB down to 30.7 GB on the first table, more than 2.5 TB reclaimed across the set, and not a single write to pay it back with. If your heap is mostly null numeric columns, it is bigger than you think, and it will compress better than you expect.
L’article Archiving 5 TB from Oracle into SQL Server: views, indexes and compression (part 2) est apparu en premier sur dbi Blog.
Is your content ready for AI?
Imagine an employee asking the company’s AI assistant, ‘What is the notice period for supplier XYZ?’
The answer comes back immediately: a clear, precise quote from the contract. It sounds right, so nobody checks it. However, the quoted contract is an expired draft. The signed amendment that changed the notice period is stored somewhere else entirely.
This is a hypothetical scenario, but I believe similar situations are already occurring. It illustrates what I think is the most underestimated aspect of AI in ECM: a poor search result is obvious, but a poor AI response can be convincing.
AI turns content quality into a risk
Before AI, poor content quality was mostly just an annoyance. Searches took longer and users had to scroll through several versions of the same document, but they usually noticed when something looked wrong. Humans were still the filter.
With AI, however, this filter becomes less effective. The assistant reads the content, selects what it deems relevant and presents a coherent summary. Users may never see the other versions, outdated drafts or documents that contradict the answer.
In my post on information debt, I wrote about the shortcuts organizations take with their content and the price they pay later. AI is often the moment when that debt becomes payable. All those duplicates, forgotten drafts, and incomplete properties are now the raw material that your AI works with.
AI does not fix messy content. It simply reads it faster, summaries it with confidence and spreads the mess further.
What AI struggles with in typical repositoriesWhen I look at how content is usually managed, I see five recurring problems.
Duplicates and near-duplicates. Which version is the truth? A person might know from experience. An AI assistant has no instinct for this unless the system gives it clear signals.
Outdated content. A procedure valid in 2019 sits next to the current one. Both are well written, and both look equally credible.
Missing or inconsistent metadata.. AI depends on context: the type of document, its status, its owner, the period it applies to. When the “Status” property is empty, or means different things in different departments, that context disappears. I covered part of this in The metadata trap. Metadata can fail by being too complex, and it can also fail by being unreliable.
Missing relationships. A contract, its amendments and its invoices tell one story. If they are stored as separate islands, the AI sees fragments. This is exactly the point I made in The forgotten power of relationships in ECM.
Unclear authority. Is this document a draft or approved? An internal opinion or an official policy? Without lifecycle information, an AI can’t tell the difference, and often neither can the user.
The good news: ECM discipline is AI readinessHere’s what I find reassuring: All the features that make content manageable in an ECM also make it trustworthy for AI:
- Lifecycle and status tell the AI what is current and approved.
- Metadata tells the AI what a document is and what it applies to.
- Relationships show which documents belong together.
- Permissions determine what each user’s AI is allowed to see.
The practices we have promoted for years, such as clear metadata, defined workflows and linked objects, were not just housekeeping measures. They provide the context that transforms a collection of documents into a knowledge base that an AI can utilize responsibly.
I made a similar point in ‘ECM + AI: achieving optimal synergy’: AI does not replace a well-structured ECM; it depends on it.
The permissions blind spotThere is one more risk that deserves its own section because it takes many organizations by surprise.
For years, content that was shared too widely was protected by obscurity. A document might be accessible to more people than intended, but it would remain hidden because no one was looking for it. AI changes that. An assistant that can search and summarize everything a user has access to will surface content in seconds that nobody knew was reachable.
Therefore, before enabling AI features, check who can access your sensitive content. This includes broad groups, inherited rights, and shared links. It is much better to discover over-sharing during a controlled review than through an AI-generated response in front of the wrong person.
A practical way forwardMaking all content ‘AI-ready’ can seem overwhelming, but it doesn’t have to be. Here is the approach I would recommend:
Start with one use case, rather than the entire repositoryChoose an area where AI could clearly add value, such as contracts, quality procedures or customer documentation. Prepare that content first.
Define what ‘authoritative’ meansDecide which statuses, document types and validity dates identify trusted content. If your team cannot answer the question ‘What counts as the official version?’, neither can the AI.
Remove the obvious noiseArchive or delete expired, duplicate and abandoned documents. This is where the lifecycle thinking from my earlier posts comes in useful.
Fix the important metadataDon’t try to perfect every property. Focus on the ones that users and AI rely on, such as type, status, owner, validity and relationships with related objects.
Review permissions before activationNOT AFTER!
Keep a human in the loop at the beginningRequire AI answers to show their sources so that users can verify them. During the pilot, review the results and collect feedback on incorrect or unexpected answers.
Measure and provide feedbackWhen an answer is incorrect, establish the reason why. This is usually due to a content problem, such as a missing status or a duplicate. Fixing these issues improves every future answer.
AI can help with the cleanup tooThis doesn’t mean that you have to have perfect content before you start. AI can help you achieve this. As I explained in M-Files AI: From Intelligent Metadata to AI Agents, it can suggest properties, classify documents, and identify similar content. This makes it a useful tool for identifying duplicates and filling in missing metadata.
A sensible approach is to work in cycles rather than gates: improve the content a little, use AI, see where it fails, then improve it again. However, it is important to have a human validate what the AI suggests, especially for properties that drive decisions.
A simple readiness checkAsk yourself the following questions about the content that AI would use:
- Can you tell whether any document is current and approved?
- Are the metadata for your key document types consistent and complete?
- Do you have an idea of how many duplicates exist of your most important content?
- Are related documents linked to each other?
- Do you know who can access your sensitive content, including via shared links?
- Can every AI answer be traced back to a source that users can verify?
- Is the quality of this content owned by someone?
If you answered “no” or “I don’t know” to several of these questions, that’s not a reason to stop. It’s a reason to start preparing!
ConclusionAI readiness isn’t a separate project. Rather, it is what good ECM practice looks like when you finally ask your content to do more than just sit in a repository. It also provides a business reason to invest in quality work that was often postponed in the past.
The organizations that benefit most from AI won’t necessarily be those with the most advanced model. They will be the ones whose content can be trusted.
Have you tested AI features on your own content yet? I’d be interested to hear what surprised you.
L’article Is your content ready for AI? est apparu en premier sur dbi Blog.
Capture VS Code requests to Ollama using MITM reverse proxy
Using mitmdump, we can create a reverse proxy in front of a local Ollama instance (which runs on port 11434) and record the incoming traffic:
Bash
mitmdump --mode reverse:http://localhost:11434 -p 11435 -w ~/Documents/ollama_requests.logBy pointing your VS Code extension to localhost:11435 instead of the default port, all HTTP traffic is silently intercepted and saved to your log file.
Next, we convert this binary log into a human-readable format, cranking up the detail level to capture the full request payloads:
Bash
mitmdump -r ollama_requests.log --set flow_detail=3 > today.txtNow you have a raw text file containing every prompt and system instruction sent by VS Code. The final step is to feed today.txt back into an LLM. You can ask it to summarize your daily coding activity, analyze the hidden system prompts injected by your extensions, or extract recurring development patterns. It is a fantastic way to reverse-engineer how AI assistants structure their context.



