<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://blogs.msdn.com/utility/FeedStylesheets/atom.xsl" media="screen"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-US"><title type="html">martinc</title><subtitle type="html">Mostly about ESENT, the database engine.</subtitle><id>http://blogs.msdn.com/b/martinc/atom.aspx</id><link rel="alternate" type="text/html" href="http://blogs.msdn.com/b/martinc/" /><link rel="self" type="application/atom+xml" href="http://blogs.msdn.com/b/martinc/atom.aspx" /><generator uri="http://telligent.com" version="5.6.50428.7875">Telligent Evolution Platform Developer Build (Build: 5.6.50428.7875)</generator><updated>2012-06-11T17:00:05Z</updated><entry><title>Cost of JetIntersectIndexes</title><link rel="alternate" type="text/html" href="http://blogs.msdn.com/b/martinc/archive/2013/04/19/cost-of-jetintersectindexes.aspx" /><id>http://blogs.msdn.com/b/martinc/archive/2013/04/19/cost-of-jetintersectindexes.aspx</id><published>2013-04-19T20:50:48Z</published><updated>2013-04-19T20:50:48Z</updated><content type="html">&lt;p&gt;We recently had a question about JetIntersectIndexes()'s performance. Here's what Brett wrote up:&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p dir="LTR" align="LEFT"&gt;&lt;span style="color: #0000ff; font-family: arial black,avant garde;"&gt;Intersect is to save disk IO at the cost of CPU time.&lt;/span&gt;&lt;/p&gt;
&lt;p dir="LTR" align="LEFT"&gt;&lt;span style="color: #0000ff; font-family: arial black,avant garde;"&gt;The cost of intersect indices, is the cost of iterating over ALL the index ranges (choice #3 below).　 That may not sound like a savings, HOWEVER index entries are stored together, and we try to maintain sequentially &amp;hellip; and so iterating over index entries generally speaking is very fast off disk.　 If you walk an index, and retrieve columns you have to get from the real record &amp;hellip; you have to jump back and forth between the index and the table, you will end up seeking into the table, and seeking is slow if the cache is not full of that data.　 Likely your cache is full of data &amp;hellip; but let me continue on the cold case points though &amp;hellip;&lt;/span&gt;&lt;/p&gt;
&lt;p dir="LTR" align="LEFT"&gt;&lt;span style="color: #0000ff; font-family: arial black,avant garde;"&gt;Some examples help imagining cold IO cases:&lt;/span&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span style="color: #0000ff; font-family: arial black,avant garde;"&gt;Base Index Range Scan &amp;ndash; Scanning or counting a range of secondary index entries (where you do not have to retrieve the main data) you can walk 100s of thousands or even single digit millions of entries in a second, because the index entry data is often sequentially laid out and a good index range tells us exactly what you&amp;rsquo;ll want to read (so we preread aggressively often in a few IOs [1] &amp;hellip; let&amp;rsquo;s say 3 IOs for argument&amp;hellip; though a range of 241k entries would probably be more than 3 IOs), and the indices often have very dense / smaller entries &amp;hellip; so we sometimes even end up being CPU bound here even if the index is cold.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="color: #0000ff; font-family: arial black,avant garde;"&gt;Base Index Range + Primary Data Scan &amp;ndash; Generally on a _&lt;em&gt;cold&lt;/em&gt;_ DB, you can walk like 100 entries / sec via a secondary index &amp;hellip; b/c while you can retrieve a range of 100s or thousands of index entries in a second (per #1), each seek into the main table is cold, requires a disk operation and random disk operations are typically ~100 IO per sec (IOPS)[2] on HDs (not SSD).　 Such a case is thoroughly random-IO disk bound.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="color: #0000ff; font-family: arial black,avant garde;"&gt;a. Best Case 3-way Index Range &amp;ndash; So if you have 3 index ranges that selects (as you have) 3k records, 240k records, and 71k records &amp;hellip; and it turns out only 1 record satisfies all 3 index ranges, then it will only cost say 3 IOs * 3 index ranges + 1 seek IO for the final data of the primary record = 9 random IOs total.　 But how much did the 240k index range save you &amp;hellip;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="color: #0000ff; font-family: arial black,avant garde;"&gt;b.&amp;nbsp;Even the most costly 240k index range probably processes in ~1 second or less (maybe a bit more, not truly sure of the speed of our insert into the sort) &amp;hellip; so if you dropped that, and it ended up giving you an extra 150 records to process (over the 1 that you&amp;rsquo;ll eventually select), you just barely lose out w/ the extra 150 random disk IOPS (1.5 seconds) from dropping that index range.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="color: #0000ff; font-family: arial black,avant garde;"&gt;c. If dropping the 240k index range gave you an extra 2999 records to process (b/c not sure if this is obvious, but implicitly we would know in this case the 71k index range didn&amp;rsquo;t add much additional selectivity over the 3k range), then that index range will have been EXTREMELY beneficial as that&amp;rsquo;d be 2999 extra seeks (an extra 30 seconds of disk IO savings) to determine the 1 true good record.　 Vs. ~1 second to process the super large index range, that&amp;rsquo;s extremely fast (30x improvement).&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="color: #0000ff; font-family: arial black,avant garde;"&gt;If dropping the 240k index range gave you NO extra records (b/c there is already only 1 record in common between the 3k and the 71k index ranges &amp;hellip; sort of the opposite of 3.b.), then the 241k is pure cost &amp;hellip; this is of course difficult to predict.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p dir="LTR" align="LEFT"&gt;&amp;nbsp;&lt;/p&gt;
&lt;p dir="LTR" align="LEFT"&gt;&lt;span style="color: #0000ff; font-family: arial black,avant garde;"&gt;Every time another index range is added it generally takes a few IOs[1] / 30 ms and let&amp;rsquo;s say 1 sec CPU / 250k entries of range.　 For a small index range of say 100 entries (where IO cost is dominant), if the selectivity of the eventual set of records you have to look up improves by only 3 records per 3 IOs, then you save those costly #2 type seeks that can only be performed at a measly 100 IOPS pace.　 For a large index range, if the selectivity of the eventual set of records improves by 100 records per 250k entries processed (CPU wise) then you save enough costly #2 type disk seeks that you win over the CPU cost.　 Keep in mind also you have 4 cores to process things, but generally speaking only 1 disk &amp;hellip; so again there CPU over disk is nicer to the system as a whole.&lt;/span&gt;&lt;/p&gt;
&lt;p dir="LTR" align="LEFT"&gt;&lt;span style="color: #0000ff; font-family: arial black,avant garde;"&gt;However, if everything is warm, generally it becomes an "even numbers" game &amp;hellip; where more entries is just more processing &amp;hellip; it probably would&amp;rsquo;ve been better to just select the most selective (smallest) index range and process them straight off by applying the left over terms (residual predicate in query parlance) through the primary record &amp;hellip; as that&amp;rsquo;s 2000 index entries + 2000 seeks (but no IO, just CPU)&amp;hellip; every index range you add is a cost / penalty of CPU processing you add to your operational time at likely little improvement.　 Which is likely what you&amp;rsquo;re seeing here &amp;hellip; but this is again another thing that is difficult to know a-priori.&lt;/span&gt;&lt;/p&gt;
&lt;p dir="LTR" align="LEFT"&gt;&lt;span style="color: #0000ff; font-family: arial black,avant garde;"&gt;[1] I am generally assuming we have generally good indices, like those over dates, auto-inc-like IDs, common terms or common names, etc.　 Keep in mind some really randomized indices, like GUID indices can have much poorer contiguity and thus can cost a lot more IO to traverse a range of them &amp;hellip; and that can change the results dramatically.&lt;/span&gt;&lt;/p&gt;
&lt;p dir="LTR" align="LEFT"&gt;&lt;span style="color: #0000ff; font-family: arial black,avant garde;"&gt;[2] This is numbers for HDs &amp;hellip; SSDs change things significantly, as they can often do 2000+ random IOPS, and don&amp;rsquo;t experience as much speed differential from sequential IOs.&lt;/span&gt;&lt;/p&gt;
&lt;p dir="LTR" align="LEFT"&gt;&amp;nbsp;&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=10412685" width="1" height="1"&gt;</content><author><name>Martin Chisholm -- MSFT</name><uri>http://blogs.msdn.com/machish_4000_hotmail.com/ProfileUrlRedirect.ashx</uri></author></entry><entry><title>JetGetObjectInfo() does not get record count by default</title><link rel="alternate" type="text/html" href="http://blogs.msdn.com/b/martinc/archive/2012/12/07/jetgetobjectinfo-does-not-get-record-count-by-default.aspx" /><id>http://blogs.msdn.com/b/martinc/archive/2012/12/07/jetgetobjectinfo-does-not-get-record-count-by-default.aspx</id><published>2012-12-07T23:29:00Z</published><updated>2012-12-07T23:29:00Z</updated><content type="html">&lt;p&gt;We recently had the following question:&lt;/p&gt;
&lt;hr style="width: 100%;" width="100%" /&gt;
&lt;p&gt;&lt;em&gt;&amp;nbsp;I am trying to get the space occupied by each table that we have by using JetGetObjectInfo. The api succeeds but it returns 0 for both number of cRecords and cPages.&lt;/em&gt;&lt;/p&gt;
&lt;p dir="LTR" align="LEFT"&gt;　&lt;/p&gt;
&lt;pre class="scroll"&gt;&lt;em&gt;&lt;code class="cplusplus"&gt;JET_OBJECTINFO tableInfo = {0};&lt;br /&gt; &lt;br /&gt; hr = ::JetGetObjectInfoA(m_spSession-&amp;gt;GetSessionId(), m_dbId, JET_objtypTable, nullptr, strTableName, &amp;amp;tableInfo, sizeof(tableInfo), JET_ObjInfo);&lt;br /&gt; &lt;br /&gt; if (FAILED(hr)) return hr;&lt;br /&gt; &lt;/code&gt;&lt;/em&gt;&lt;/pre&gt;
&lt;p dir="LTR" align="LEFT"&gt;&lt;em&gt;JetGetTableInfo didnt help as well.&lt;/em&gt;&lt;/p&gt;
&lt;p dir="LTR" align="LEFT"&gt;　&lt;/p&gt;
&lt;p dir="LTR" align="LEFT"&gt;&lt;em&gt;However if i open the cursor and do a&amp;nbsp;&lt;span style="font-family: Consolas,Lucida Console; font-size: x-small;"&gt;&lt;span style="font-family: Consolas,Lucida Console; font-size: x-small;"&gt;JetIndexRecordCount &lt;/span&gt;&lt;/span&gt;&lt;span style="font-family: Tahoma,Times New Roman; font-size: x-small;"&gt;&lt;span style="font-family: Tahoma,Times New Roman; font-size: x-small;"&gt;I see lots of records present in this table/index. &lt;/span&gt;&lt;/span&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p dir="LTR" align="LEFT"&gt;&lt;em&gt;If i use JetGetIndexInfo on the clustered index I get the space occupied (using JET_IdxInfoSpaceAlloc) i get 80 as result (I beleive this is in pages).&lt;/em&gt;&lt;/p&gt;
&lt;p dir="LTR" align="LEFT"&gt;　&lt;/p&gt;
&lt;p dir="LTR" align="LEFT"&gt;&lt;em&gt;Please let me know if i am missing some options that ought to be passed (grbits in the tableInfo ?).&lt;/em&gt;&lt;/p&gt;
&lt;p dir="LTR" align="LEFT"&gt;　&lt;/p&gt;
&lt;p dir="LTR" align="LEFT"&gt;&lt;em&gt;Thanks,&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr style="width: 100%;" width="100%" /&gt;
&lt;p&gt;It's not obvious, but you need to call JetComputeStats() first.&lt;/p&gt;
&lt;p&gt;Calculating these numbers can be expensive to calculate on a large table, which is why the default behaviour is to leave them at zero.&lt;/p&gt;
&lt;p&gt;Documentation on&amp;nbsp;JetComputeStats() can be found at &lt;a href="http://msdn.microsoft.com/en-us/library/windows/desktop/gg269192(v=exchg.10).aspx"&gt;&lt;span style="font-family: Tahoma,Times New Roman; font-size: x-small;"&gt;&lt;span style="font-family: Tahoma,Times New Roman; font-size: x-small;"&gt;&lt;span lang="EN"&gt;http://msdn.microsoft.com/en-us/library/windows/desktop/gg269192(v=exchg.10).aspx&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;.&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=10375703" width="1" height="1"&gt;</content><author><name>Martin Chisholm -- MSFT</name><uri>http://blogs.msdn.com/machish_4000_hotmail.com/ProfileUrlRedirect.ashx</uri></author></entry><entry><title>Introduction to Indices</title><link rel="alternate" type="text/html" href="http://blogs.msdn.com/b/martinc/archive/2012/09/10/introduction-to-indices.aspx" /><id>http://blogs.msdn.com/b/martinc/archive/2012/09/10/introduction-to-indices.aspx</id><published>2012-09-10T07:33:45Z</published><updated>2012-09-10T07:33:45Z</updated><content type="html">&lt;p&gt;ESE is an ISAM engine -- Indexed Sequential Access Method. Wikipedia has an article on ISAMs: &lt;a href="http://en.wikipedia.org/wiki/ISAM"&gt;http://en.wikipedia.org/wiki/ISAM&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;ESE&amp;nbsp;allows you to insert data in a structured manner, with data types such as integer or strings. Then you need to be able to find the data, which is where indexing comes in. As one co-worker put it '80% of ESE's coolness is in its indexing'.&lt;/p&gt;
&lt;p&gt;ESE stores records in a B+tree (&lt;a href="http://en.wikipedia.org/wiki/B%2B_tree"&gt;http://en.wikipedia.org/wiki/B%2B_tree&lt;/a&gt;). This tree is ordered by the Primary Key. You can either use the default order (an internally-incrementing number, so any new records inserted just end up at the end), or you can explicitly define a primary index (sometimes called 'Clustered Index') by specifying JET_bitIndexPrimary to the index definition. This is very beneficial when you have a unique property that you know will be accessed sequentially. (For example sorting by Employee ID or by Social Security Number [US] or Social Insurance Number [Canada] could be useful. But a primary index on employee name wouldn't be good because two people may have the same name.)&lt;/p&gt;
&lt;p&gt;It's usually more interesting to be able to sort on properties other than the Primary Index. For example, sorting by employee names can be useful. This can't be a primary index, since full names aren't unique. You may want to create an index over "+LastName\0+FirstName\0\0". And maybe an index over "+Salary\0\0". These would be secondary indices.&lt;/p&gt;
&lt;p&gt;But how do secondary indices work? They are also B+trees. Take the following data:&lt;/p&gt;
&lt;table border="1" align="left"&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;EmployeeId&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;LastName&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;FirstName&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Salary&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Pertwee&lt;/td&gt;
&lt;td&gt;Jon&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Agyeman&lt;/td&gt;
&lt;td&gt;Freema&lt;/td&gt;
&lt;td&gt;2200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;Gillan&lt;/td&gt;
&lt;td&gt;Karen&lt;/td&gt;
&lt;td&gt;2200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;202&lt;/td&gt;
&lt;td&gt;Smith&lt;/td&gt;
&lt;td&gt;Matt&lt;/td&gt;
&lt;td&gt;2000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;(Obviously 'Salary' is not unique, but 'EmployeeId' is.)&lt;/p&gt;
&lt;p&gt;The Primary Index over 'EmployeeId' will have the primary keys of {3, 5, 101, 202}. The contents in that tree will be the records themselves.&lt;/p&gt;
&lt;p&gt;The LastName/FirstName index would be: { [ "AGYEMAN\0FREEMA\0", 5 ], [ "GILLAN\0KAREN\0", 101 ], [ "PERTWEE\0JON\0", 3 ], [ "SMITH\0MATT\0", 202 ] }.&lt;/p&gt;
&lt;p&gt;The Salary index would be { [ 1000, 3],&amp;nbsp;[2000, 202], [2200, 5], [2200, 101] }.&lt;/p&gt;
&lt;p&gt;Some&amp;nbsp;things to note are:&lt;br /&gt;-The contents of the secondary indices are the primary keys.&lt;br /&gt;-By default, ESE converts ANSI text to uppercase for sorting. (Unicode text is treated differently.)&lt;br /&gt;-The Salary index has a couple of duplicate entries. it's not a Unique index, so this is OK. The tie-breaker isthe value of the Primary Key.&lt;/p&gt;
&lt;p&gt;If one was to insert a new record (e.g.&amp;nbsp;{ 55, "Baker", "Tom", 3000 } ), then a few things happen:&lt;br /&gt;-By inserting in the middle, if there is no space left on the database page, then it may be necessary to split the page in two. Doing this page split may result in some discontiguous data. So specifying an index density of less than 100 when creating the index can avoid splits. (Data contiguity in general should be another blog topic...)&lt;br /&gt;-It also requires updating the two secondary indices (including uniqueness checks). The more secondary indices you have, the more expensive this will be.&lt;/p&gt;
&lt;p&gt;The uniqueness check is done at JetUpdate() time, not JetSetColumn/JetSetColumns.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Conclusion:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That just scratches the surface of indexing. Secondary indices are useful, but can also expensive. Use them wisely!&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=10347752" width="1" height="1"&gt;</content><author><name>Martin Chisholm -- MSFT</name><uri>http://blogs.msdn.com/machish_4000_hotmail.com/ProfileUrlRedirect.ashx</uri></author></entry><entry><title>Sharp Corner: Close a table after creating it</title><link rel="alternate" type="text/html" href="http://blogs.msdn.com/b/martinc/archive/2012/06/23/sharp-corner-close-a-table-after-creating-it.aspx" /><id>http://blogs.msdn.com/b/martinc/archive/2012/06/23/sharp-corner-close-a-table-after-creating-it.aspx</id><published>2012-06-24T01:02:00Z</published><updated>2012-06-24T01:02:00Z</updated><content type="html">&lt;p&gt;The ESENT API has existed for a while, so there are quite a few oddities to it.&lt;/p&gt;
&lt;p&gt;After creating a database with ESENT, you need to close the returned JET_TABLEID.&lt;/p&gt;
&lt;p&gt;The original way to create a usable table was to create the table with &lt;a href="http://blogs.msdn.com/controlpanel/blogs/posteditor.aspx/JetCreateTable" title="http://msdn.microsoft.com/en-us/library/windows/desktop/gg269210(v=exchg.10).aspx"&gt;JetCreateTable&lt;/a&gt;, add some columns with repeated calls to &lt;a href="http://blogs.msdn.com/controlpanel/blogs/posteditor.aspx/JetAddColumn" title="http://msdn.microsoft.com/en-us/library/windows/desktop/gg294122(v=exchg.10).aspx"&gt;JetAddColumn&lt;/a&gt;, then index the columns with &lt;a href="http://blogs.msdn.com/controlpanel/blogs/posteditor.aspx/JetCreateIndex" title="http://msdn.microsoft.com/en-us/library/windows/desktop/gg294099(v=exchg.10).aspx"&gt;JetCreateIndex&lt;/a&gt;. But in a multi-threaded concurrent program, you wouldn't want two threads to try and do the same thing, so the returned JET_TABLEID from JetCreateTable() is opened with exclusive access. No other JET_SESID can open that table.&lt;/p&gt;
&lt;p&gt;Fast-forward to the introduction of &lt;a href="http://blogs.msdn.com/controlpanel/blogs/posteditor.aspx/JetCreateTableColumnIndex" title="http://msdn.microsoft.com/en-us/library/windows/desktop/gg269343(v=exchg.10).aspx"&gt;JetCreateTableColumnIndex&lt;/a&gt; (and later &lt;a href="http://blogs.msdn.com/controlpanel/blogs/posteditor.aspx/JetCreateTableColumnIndex3" title="http://msdn.microsoft.com/en-us/library/windows/desktop/gg294079(v=exchg.10).aspx"&gt;JetCreateTableColumnIndex3&lt;/a&gt;, and in Windows 8 JetCreateTableColumnIndex4 [not yet on MSDN]). This API takes a large &lt;a href="http://blogs.msdn.com/controlpanel/blogs/posteditor.aspx/JET_TABLECREATE" title="http://msdn.microsoft.com/en-us/library/windows/desktop/gg294146(v=exchg.10).aspx"&gt;JET_TABLECREATE&lt;/a&gt; structure that holds the information needed for the table, column, and indices. Everything can be done with a single API call, without a need to do a bunch of loops.&lt;/p&gt;
&lt;p&gt;But take a closer look at the JET_TABLECREATE structure:&lt;/p&gt;
&lt;pre class="scroll"&gt;&lt;code class="cplusplus"&gt;typedef struct tagJET_TABLECREATE {&lt;br /&gt; unsigned long cbStruct;&lt;br /&gt; tchar* szTableName;&lt;br /&gt; tchar* szTemplateTableName;&lt;br /&gt; unsigned long ulPages;&lt;br /&gt; unsigned long ulDensity;&lt;br /&gt; JET_COLUMNCREATE* rgcolumncreate;&lt;br /&gt; unsigned long cColumns;&lt;br /&gt; JET_INDEXCREATE* rgindexcreate;&lt;br /&gt; unsigned long cIndexes;&lt;br /&gt; JET_GRBIT grbit;&lt;br /&gt; JET_TABLEID tableid;&lt;br /&gt; unsigned long cCreated;&lt;br /&gt; } JET_TABLECREATE;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are two output variables there: tableid and cCreated. cCreated returns the number of objects returned (e.g. when create a table with 5 columns and 3 indices, cCreated will be 1+5+3). This helps narrow down which table/column/index had a problem if the API failed. tableid is the JET_TABLEID of the newly created table.&lt;/p&gt;
&lt;p&gt;We don't like to have multiple implementations of the code lying around, so internally JetCreateTable() will call the same function as JetCreateTableColumnIndex.&amp;nbsp;The tableid was opened exclusively, to be just like the JetCreateTable/JetAddColumn/JetCreateIndex combination, it was also opened exclusively. So now it should be closed with JetCloseTable().&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=10323263" width="1" height="1"&gt;</content><author><name>Martin Chisholm -- MSFT</name><uri>http://blogs.msdn.com/machish_4000_hotmail.com/ProfileUrlRedirect.ashx</uri></author></entry><entry><title>ManagedEsent is Metro Compliant</title><link rel="alternate" type="text/html" href="http://blogs.msdn.com/b/martinc/archive/2012/06/11/managedesent-is-metro-compliant.aspx" /><id>http://blogs.msdn.com/b/martinc/archive/2012/06/11/managedesent-is-metro-compliant.aspx</id><published>2012-06-12T00:00:05Z</published><updated>2012-06-12T00:00:05Z</updated><content type="html">&lt;p&gt;I'm inaugurating this blog with an announcement: ManagedEsent is Metro Compliant.&lt;/p&gt;
&lt;p&gt;What does this mean to you? If you write a Metro program in C#, you can grab ManagedEsent from CodePlex&amp;nbsp;(&lt;a href="http://managedesent.codeplex.com/SourceControl/list/changesets"&gt;http://managedesent.codeplex.com&lt;/a&gt;)&amp;nbsp;and you can use your favourite ESENT APIs from from C#, and still pass certification.&lt;/p&gt;
&lt;p&gt;The update is currently available only in source form (&lt;a href="http://managedesent.codeplex.com/SourceControl/list/changesets"&gt;http://managedesent.codeplex.com/SourceControl/list/changesets&lt;/a&gt;). Binaries will be coming.&lt;/p&gt;
&lt;p&gt;Don't hesitate to start new discussions or bug reports&amp;nbsp;in the Discussions area (&lt;a href="http://managedesent.codeplex.com/discussions"&gt;http://managedesent.codeplex.com/discussions&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;-martin&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=10318617" width="1" height="1"&gt;</content><author><name>Martin Chisholm -- MSFT</name><uri>http://blogs.msdn.com/machish_4000_hotmail.com/ProfileUrlRedirect.ashx</uri></author></entry></feed>