Search

Top 60 Oracle Blogs

Recent comments

Oakies Blog Aggregator

Announcing SLOB 2.5.3

This is just a quick blog post to inform readers that SLOB 2.5.3 is now available at the following webpage: click here.

SLOB 2.5.3 is a bug fix release. One of the fixed bugs has to do with how SLOB sessions get connected to RAC instances. SLOB users can surely connect to the SCAN service but for more repeatable testing I advise SLOB 2.5.3 and SQL*Net services configured one per RAC node. This manner of connectivity establishes affinity between schemas and RAC nodes. For example, repeatability is improved if sessions performing SLOB Operations against, say, user7’s schema, it is beneficial to do so connected to the same RAC node as you iterate through your testing.

The following is cut and pasted from SLOB/misc/sql_net/README:

The tnsnames.ora in this directory offers an example of
service names that will allow the user to test RAC with
repeatable results. Connecting SLOB sessions to the round
robin SCAN listener will result in SLOB sessions connecting
to random RAC nodes. This is acceptable but not optimal and
can result in varying run results due to slight variations
in sessions per RAC node from one test to another.
As of SLOB 2.5.3, runit.sh uses the SQLNET_SERVICE_BASE and
SQLNET_SERVICE_MAX slob.conf parameters to sequentially
affinity SLOB threads (Oracle sessions) to numbered service
names. For example:
SQLNET_SERVICE_BASE=rac
SQLNET_SERVICE_MAX=8
With these assigned values, runit.sh will connect the first
SLOB thread to rac1 then rac2 and so forth until rac8 after
which the connection rotor loops back to rac1. This manner
of RAC affinity testing requires either a single SLOB
schema (see SLOB Single Schema Model in the documentaion)
or 8 SLOB schemas to align properly with the value assigned
to SQLNET_SERVICE_MAX. The following command will connect
32 SLOB threads (Oracle sessions) to each RAC node in an
8-node RAC configuration given the tnsnames.ora example
file in this directory:
$ sh ./runit.sh -s 8 -t 32

Oracle 19c Automatic Indexing: Non-Equality Predicates Part II (Let’s Spend The Night Together)

In my previous post in this series, I discussed out Automatic Indexing currently does not consider Non-Equality predicates. Automatic Indexing will index columns based only on Equality predicates. So how does Oracle handle the scenario when an SQL has a mixture of both Equality and Non-Equality predicates? I’ll begin by creating two very similar tables, […]

VirtualBox 6.1.18

VirtualBox 6.1.18 has been released.

The downloads and changelog are in the usual places.

I’ve installed it on Windows 10, macOS Big Sur and Oracle Linux 7 hosts with no problems.

I’ll be running new Packer builds for the oraclebase/oracle-7 and oraclebase/oracle-8 vagrant boxes, so they should appear with the new version of the guest additions over the next day or so.

Cheers

Tim…

Links:

The post VirtualBox 6.1.18 first appeared on The ORACLE-BASE Blog.


VirtualBox 6.1.18 was first posted on January 20, 2021 at 3:50 pm.
©2012 "The ORACLE-BASE Blog". Use of this feed is for personal non-commercial use only. If you are not reading this article in your feed reader, then the site is guilty of copyright infringement.

Hint Errors

This is a list of possible explanations of errors that you might see in the Hint Report section of an execution plan. It’s just a list of the strings extracted from a chunk of the 19.3 executable around the area where I found something I knew could be reported, so it may have some errors and omissions – but there are plenty of things there that might give you some idea why (in earlier versions of Oracle) you might have seen Oracle “ignoring” a hint:

internally generated hint is being cleared
hint conflicts with another in sibling query block
hint overridden by another in parent query block
conflicting optimizer mode hints
duplicate hint
all join methods are excluded by hints
index specified in the hint doesn't exist
index specified in hint cannot be parallelized
incorrect number of indexes for AND_EQUAL
partition view set up
FULL hint is same as INDEX_FFS for IOT
access path is not supported for IOT
hint on view cannot be pushed into view
hint is discarded during view merging
duplicate tables in multi-table hint
conditions failed for array vector read
same QB_NAME hints for different query blocks
rejected by IGNORE_OPTIM_EMBEDDED_HINTS
specified number must be positive integer
specified number must be positive number
specified number must be >= 0 and <= 1
hint is only valid for serial SQL
hint is only valid for slave SQL
hint is only valid for dyn. samp. query
hint is only valid for update join ix qry
opt_estimate() without value list
opt_estimate() with conflicting values spec
hint overridden by NO_QUERY_TRANSFORMATION
hinted query block name is too long
hinted bitmap tree wasn't fully resolved
bitmap tree specified was invalid
Result cache feature is not enabled
Hint is valid only for select queries
Hint is not valid for this query block
Hint cannot be honored
Pred reorder hint has semantic error
WITH_PLSQL used in a nested query
ORDER_SUBQ with less than two subqueries
conflicting OPT_PARAM hints
conflicting optimizer_feature_enable hints
because of _optimizer_ignore_parallel_hints
conflicting JSON_LENGTH hints

CBO Example

A little case study based on an example just in on the Oracle-L list server. This was supplied with a complete, working, test case that was small enough to understand and explain very quickly.

The user created a table, and used calls to dbms_stats to fake some statistics into place. Here, with a little cosmetic editing, is the code they supplied.

set serveroutput off
set linesize 180
set pagesize 60
set trimspool on

drop table t1 purge;

create table t1 (id number(20), v varchar2(20 char));
create unique index pk_id on t1(id);
alter table t1 add (constraint pk_id primary key (id) using index pk_id enable validate);
exec dbms_stats.gather_table_stats(user, 't1');
 
declare
        srec               dbms_stats.statrec;
        numvals            dbms_stats.numarray;
        charvals           dbms_stats.chararray;
begin
  
        dbms_stats.set_table_stats(
                ownname => user, tabname => 't1', numrows => 45262481, numblks => 1938304, avgrlen => 206
        );

        numvals := dbms_stats.numarray (1, 45262481);
        srec.epc:=2;
        dbms_stats.prepare_column_values (srec, numvals);
        dbms_stats.set_column_stats (
                ownname => user, tabname => 't1', colname => 'id', 
                distcnt => 45262481, density => 1/45262481,
                nullcnt => 0, srec => srec, avgclen => 6
        );

        charvals := dbms_stats.chararray ('', '');
        srec.epc:=2;
        dbms_stats.prepare_column_values (srec, charvals);
        dbms_stats.set_column_stats(
                ownname => user, tabname => 't1', colname => 'v', 
                distcnt => 0,  density => 0, 
                nullcnt => 45262481, srec => srec, avgclen => 0
        );
        dbms_stats.set_index_stats( 
                ownname => user, indname =>'pk_id', numrows => 45607914, numlblks => 101513,
                numdist => 45607914, avglblk => 1, avgdblk => 1, clstfct => 33678879, indlevel => 2
        );
end;
/
 
variable n1 nvarchar2(32)
variable n2 number

begin
        :n1 := 'D';
        :n2 := 50;
end;
/
 

select 
        /*+ gather_plan_statistics */ 
        * 
from    ( 
        select  a.id col0,a.id col1
        from    t1 a
        where   a.v = :n1 
        and     a.id > 1
        order by 
                a.id 
        ) 
where 
        rownum <= :n2 
;
 
select * from table(dbms_xplan.display_cursor(null,null,'allstats last cost peeked_binds '));

From Oracle’s perspective the table has 45M rows, with a unique sequential key starting at 1 in the id column. The query looks like a pagination query, asking for 50 rows, ordered by id. But the in-line view asks for rows where id > 1 (which, initiall, means all of them) and applies a filter on the v column.

Of course we know that v is always null, so in theory the predicate a.v = :n1 is always going to return false (or null, but not true) – so the query will never return any data. However, if you read the code carefully you’ll notice that the bind variable v has been declared as an nvarchar2() not a varchar2().

Here’s the exection plan I got on an instance running 19.3 – and it’s very similar to the plan supplied by the OP:

----------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name  | Starts | E-Rows | Cost (%CPU)| A-Rows |   A-Time   |
----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |       |      1 |        |  3747 (100)|      0 |00:00:00.01 |
|*  1 |  COUNT STOPKEY                |       |      1 |        |            |      0 |00:00:00.01 |
|   2 |   VIEW                        |       |      1 |     50 |  3747   (1)|      0 |00:00:00.01 |
|*  3 |    TABLE ACCESS BY INDEX ROWID| T1    |      1 |    452K|  3747   (1)|      0 |00:00:00.01 |
|*  4 |     INDEX RANGE SCAN          | PK_ID |      0 |   5000 |    14   (0)|      0 |00:00:00.01 |
----------------------------------------------------------------------------------------------------

Peeked Binds (identified by position):
--------------------------------------
   2 - :2 (NUMBER): 50

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter(ROWNUM<=:N2)
   3 - filter(SYS_OP_C2C("A"."V")=:N1)
   4 - access("A"."ID">1)

The question we were asked was this: “Why does the optimizer estimate that it will return 5,000 entries from the index range scan at operation4?”

The answer is the result of combining two observations.

First: In the Predicate Information you can see that Oracle has applied a character-set conversion to the original predicate “a.v = :n1” to produce filter(SYS_OP_C2C(“A”.”V”)=:N1). The selectivity of “function of something = bind value” is one of those cases where Oracle uses one of its guesses, in this case 1%. Note that the E-rows estimate for operation 3 (table access) is 452K, which is 1% of the 45M rows in the table.

In real life if you had optimizer_dynamic_sampling set at level 3, or had added the hint /*+ dynamic_sampling(3) */ to the query, Oracle would sample some rows to avoid the need for guessing at this point.

Secondly: the optimizer has peeked the bind variable for the rownum predicate, so it is optimizing for 50 rows (basically doing the arithmetic of first_rows(50) optimisation). The optimizer “knows” that the filter predicate at the table will eliminate all but 1% of the rows acquired, and it “knows” that it has to do enough work to find 50 rows in total – so it can calculate that (statistically speaking) it has to walk through 5,000 (= 50 * 100) index entries to visit enough rows in the table to end up with 50 rows.

Next Steps (left as exercise)

Once you’ve got the answer to the question “Why is this number 5,000?”, you might go back and point out that the estimate for the table access was 95 times larger than the estimate for the number of rowids selected from the index and wonder how that could be possible. (Answer: that’s just one of the little defects in the code for first_rows(n).)

You might also wonder what would have happened in this model if the bind variable n1 had been declared as a varchar2() rather than an nvarchar2() – and that might have taken you on to ask yet another question about what the optimizer was playing at.

Once you’ve modelled something that is a little puzzle there’s always scope for pushing the model a little further and learning a little bit more before you file the model away for testing on the next version of Oracle.

Vagrant and Docker Builds: ORDS 20.4, SQLcl 20.4 and Database Patches

https://oracle-base.com/blog/wp-content/uploads/2019/12/ords-2-258x300.png 258w" sizes="(max-width: 196px) 85vw, 196px" />

The January Oracle quarterly patches were released yesterday, which prompted me to do some new builds.

We got Oracle REST Data Services (ORDS) 20.4 and SQLcl 20.4, which I use in a number of my Vagrant and Docker builds, so I updated them and ran some builds.

The Vagrant database builds, which include ORDS, can be found here.

The Docker ORDS builds can be found here.

https://oracle-base.com/blog/wp-content/uploads/2019/12/sqlcl-295x300.png 295w, https://oracle-base.com/blog/wp-content/uploads/2019/12/sqlcl-57x57.png 57w" sizes="(max-width: 208px) 85vw, 208px" />

I also updated Tomcat to 9.0.41. I assume we will get the new version of the JDK from AdoptOpenJDK in the next couple of days, so there will be another update coming soon.

Once I finished those I decided to try out the Oracle database 19c (19.10) OJVM+DB combo patch on a single instance build. That went fine. You can see that build here.

Since that went OK, I figured it was worth trying to update my OL8 19c RAC build with the 19.10 OJVM+GI combo patch. That also went fine. You can see that build there.

I wasn’t really expecting to cover so much ground so quickly, but that’s the great thing about automation. </p />
</p></div>

    	  	<div class=

Sourdough – Making a Loaf

<<– Creating the Sourdough Starter

Anyone who follows me on Twitter knows I like making sourdough bread. For me, a sourdough loaf is a real treat. I love the combination of a thick, crunchy crust and the soft, strong-flavoured inside. I’ve been asked a few times how I make my bread and I keep saying I will write it up. This blog post is the fulfilment of that promise.

Making sourdough is a longer, more complex baking process than most modern versions of baking bread, but it is actually a very old method of baking and was probably the main method used by the peasant and working classes over the last few hundred years. It takes several hours to make sourdough. I start mine in the evening and bake it in the morning.

Work is stressful (even working in I.T. from home), this pandemic is stressful, baking a nice loaf of bread helps balance that stress.

A key part of the process is that you need a “starter”, a mixture of flour, water, and actively growing yeast. I did a long and detailed post on creating a starter about a month or so ago. If you created a starter then and have been feeding it since, it’s well past time to make a loaf!

Get the Starter Active

If the starter mixture is in the fridge, take it out of the fridge several hours before you are going to use it. If I am making my dough in the evening (my usual method so it can prove overnight) I take the starter out the fridge about noon.

A few hours before you are going to make your dough (usually 6 hours or so for me), mix up 200 grams of strong, white bread flour with tepid water so it is a similar consistency to porridge, add it to the starter and give it a good stir.

This should help get the starter really active and, after a couple of hours, you should see bubbles in the mixture and the volume will increase. I do not seal the jar during this process, I leave it with the lid over the top of the jar but not clipped or screwed down.

Making the Initial Dough

I’ll give you two recipes for making the dough. The first is from a man called Paul Hollywood, who is a very well known and successful baker in the UK. He is one of the judges on “The Great British Bake off“, which is one of the most popular TV programs in the UK. I know the program has been syndicated across the globe, with over 25 countries showing their own version, and a couple showing the UK original. The second recipe is mine, which is derived from Paul Hollywood’s. I increased the size of the loaf as I wanted something to provide sandwiches for 2 people for 2 days and I found a little more salt and a lower percentage of starter gave results I preferred. Less starter seems to give a better final rise to the loaf. Please note – Paul Hollywood is a considerably better baker than I! Perhaps try his recipe first.

Paul Hollywood recipe

  • 375g Strong white bread flour
  • 250g sourdough starter
  • 7g salt
  • 130-175 ml tepid water
  • a teaspoon of olive oil

Martin Widlake recipe

  • 500g strong white bread flour
  • 200g sourdough starter
  • 10g salt (but no more!)
  • 7g sugar
  • 200-220ml tepid water
  • a teaspoon of olive oil

 

 

The below is based on my recipe

I have a little plastic jug for measuring the water. Before I put any water in it I put the 10 grams of normal, fine table salt (1). Do not go above 10g of salt in 700g total flour & starter as too much salt inhibits the rise of the loaf. I’m adding about as much salt as you can without this happening.  I also add a teaspoon of sugar (7 grams) as I feel it balances the sour of the loaf and slightly boosts the loaf flavour. Skip this if you like.
https://mwidlake.files.wordpress.com/2021/01/screenhunter_-642.jpg?w=406... 406w, https://mwidlake.files.wordpress.com/2021/01/screenhunter_-642.jpg?w=129... 129w, https://mwidlake.files.wordpress.com/2021/01/screenhunter_-642.jpg?w=258... 258w" sizes="(max-width: 204px) 100vw, 204px" />

I then put 500 grams of strong bread flour into the mixer bowl (see later for some variations to 500g of flour). As I add the flour I also dribble in the salt/sugar mix. This is to help it all mix in evenly. I found that if I just chucked the salt in after all the flour, again the rise could be problematic and the bread seemed to be a bit patchy in it’s flavour. Give the flour with the salt/sugar in a quick swirl with a spoon or something.

https://mwidlake.files.wordpress.com/2021/01/screenhunter_-645.jpg?w=522... 522w, https://mwidlake.files.wordpress.com/2021/01/screenhunter_-645.jpg?w=130... 130w" sizes="(max-width: 261px) 100vw, 261px" />

I now add 200 grams of sourdough mixture and about 100ml of the tepid water. I do not add it all as I use a food mixer to initially combine my dough. We have a Kenwood Chef that is 40+ years old. To make bread dough in a food mixer you need a dough hook. The one you see in the picture by the recipes is only a few years old, it is coated with Teflon to help the dough to not stick to it.

The mixer can throw little fountains of dry ingredients out of the bowl so I put a towe over the whole thing. If you do this, make absolutely sure the towel is not going to get caught on the dough hook/mixer! With the mixer on it’s lowest setting, I slowly add more of the water to each side of the bowl so that the ingredients combine. I have found that as the dough mixture gets towards the consistency I want, or is damper than I am aiming for, it wraps around the dough hook and no longer mixes! It just wizzes around with the hook.  This is why I added the water slowly and keep about 20-30ml in reserve. Then, when all the ingredients are well mixed but it is not quite forming a single ball, I add the last of the water and keep the mixer running until the dough does wrap around the hook and stay on it. Take it off the hook and make it into a rough ball, as shown In the picture of the mixer.

You can mix it all by hand, which is fun, but your hands get really messy and it takes longer. If you do mix it all by hand, add the water bit by bit until the dough is quite sticky.

I now put a little olive oil, half a teaspoon is all, on a thoroughly cleaned work surface and spread it around  into a 20-30cm circle. I drop the dough in the centre of this and I knead it by hand to finish it off and get a smooth consistency in the dough. Different people like to mix their dough in different ways. I push into it with the heel of my hand, stretching it against the work surface, and then fold it over a little and push into it again. I do this with just the one hand in a regular rhythm of about one one push a second, slowly rotating the dough ball and moving it around so I am working all of the ball. I swap hands occasionally for a full upper-body workout…

Other people slap the dough onto the work surface or throw it down, others squidge it out with both hands and then fold-and-squidge. Do what seems right to you. There are lots of videos on the internet.

The whole aim is to get all the ingredients mixed in smoothly and keep going until the dough is a little elastic. Apparently the best test as to whether you have worked the dough enough is that you can stretch some thin with your fingers and see light coming through it. I don’t do this, it does not seem to work well with my dough, maybe as I do not add enough liquid, maybe because sourdough is a little different. I know it is ready as it…. feels ready. Smooth, not rubbery, but with some stretch to it. Because I use a machine to initially mix and knead the dough I only have to hand knead it for 5 minutes. If you mix the dough by hand then you will need to knead it for 10, 15 minutes. Maybe more.

The whole idea of the kneading is to get some of the protein in the mix, the gluten, to form long chains which give the final loaf it’s structure of a soft and flexible material. If you over knead the dough then the bread will not rise so well and the bread will be rubbery and dense. You don’t want rubbery, dense bread.

Proving the Dough

Once your bread is kneaded to the consistency you want, you have to let it prove – which means left alone to grow. You prove the dough twice.

Use the other half a teaspoon of oil to lightly oil the inside of the mixing bowl. The only reason for the oil is to stop the dough sticking. Put the dough ball into the bowl and cover with clingfilm or similar. I use a clear, plastic shower cap that I can re-use dozens of times as (a) it’s so easy to pop it over the bowl and (b) less plastic waste.

https://mwidlake.files.wordpress.com/2021/01/screenhunter_-627.jpg?w=444... 444w, https://mwidlake.files.wordpress.com/2021/01/screenhunter_-627.jpg?w=138... 138w, https://mwidlake.files.wordpress.com/2021/01/screenhunter_-627.jpg?w=275... 275w" sizes="(max-width: 223px) 100vw, 223px" />https://mwidlake.files.wordpress.com/2021/01/screenhunter_-628.jpg?w=600... 600w, https://mwidlake.files.wordpress.com/2021/01/screenhunter_-628.jpg?w=150... 150w" sizes="(max-width: 300px) 100vw, 300px" />

You need to keep the dough at about room temperature – between 18C and 22C – for several hours. Less time if it is warmer, more time if it is cooler. I make my dough about 8-10pm in the evening and leave it overnight, near a radiator that will come on in the morning. This seems to work for my dough.

During the proving stage the yeast in the dough consumes sugars (the sugars come from the starch in the flour being broken down) and they produce carbon dioxide (CO2). this is what makes the dough grow and become soft.

In this first prove of the dough it should doubled to tripled in volume, and become soft and spongey to a light touch. Sticking a finger in it will leave a dent that only partly fills in.

Lightly dust a clean, dry area on your work surface with plain or bread flour and turn the dough out of the bowl it has proved in onto the area. I lightly dust one side of the bowl to stop the dough sticking to it and I ease the dough from the sides and bottom of the bowl with a small, flexible spatula – one of those made of silicon or soft, heat resistant plastic. In the picture above of the dough on the work surface you can see bubbles in it – this is from the CO2.

 

You now need to “knock back” the dough – knead  it all over with your knuckles or, like I do, give it 30 seconds of kneading like you did when you first made the dough. Some instructions tell you to do things like make a ball after knocking it back and  tuck the dough down under the ball and into the bottom of it. I think these are to create little air pockets in the dough that make the large voids you get in posh hippster café sourdough. I don’t want those large voids. I keep the flour dusting to a minimum and push the dough together well to avoid any air gaps or having any folds in the dough which do not “heal” (stick to each other).

You now need to let the dough prove for a second time. I use a “banneton” for this, a special wicker or similar material bowl that is specifically for the final proving of bread. They also impart a nice pattern on the loaf. Dust whatever bowl or banneton you are using well, put the dough into it and push it down firmly. Lightly dust the top and then cover a plastic bag or similar. You want the bag to be above the dough so when it rises it does not contact the bag, as it will stick to it.  I put the showercap I used earlier back over it, with the damp side inwards to stop the top of the bread drying out too much. Put somewhere warm and leave for two hours. If the house is not that warm, I put the oven on and set it to 50C, then turn it off and pop the loaf in that. If you are dead posh you might find your oven has a proving oven compartment or a plate warmer you can use.

After a couple of hours the dough should have risen a little again and have a smooth top. It is now ready to bake

Baking the Bread.

A key to getting a good bake where the bread rises evenly and you get a good, strong crust is moisture. You need the atmosphere around the loaf to be damp for the first 20 minutes or so of baking.

I’ve achieved this with two methods – baking in the oven with a tray of water, and using a Dutch Oven.

In the Oven With a Tray of Water.

Pre-heat the oven to 220C and put a shallow tray on the lower shelf.

Heavily dust a baking tray with flour, or flour and semolina (semolina is better at preventing the loaf from sticking, but I find flour on it’s own works just fine and I stopped using the semolina as I’m lazy). Carefully tip the loaf out on the tray and slash the top several times. I have a special, small, gentle serrated knife just for this, it seems to work better than a smooth blade. He’s called Mr Slashy the knife. This scouring allows the crust to expand more easily during the cooking.

 

Dust lightly with flour and immediately put the loaf into the oven, and put about 500ml of warm water in the shallow tray. This will create steam as the bread cooks.

Cook at 220C for 30 minutes and then turn the oven down to 200C and cook for a further 15-20 minutes. The bread should have risen and turned a lovely golden brown. You can test if it is done by tapping the bottom of the loaf, it should sound hollow. If, like me, you like your bread slightly darker with a stronger crust, extend the higher temperature period from 30 minutes to 35, 40 minutes.

Take the loaf out and move it onto a wire rack to cool.

In the example I show, the loaf is a weird shape. I think this is because, with this loaf, I forgot to put the water in the oven with the loaf, then added cold water to the tray, not warm. As a result there was not enough moisture, the crust formed early and the still-expanding loaf could no longer grow and burst out the side of the crust. If this happens to a lot of your loaves, try scoring more or gently wetting the top and sides of the loaf before the final dust of flour.

It tasted just fine!

In a Dutch Oven.

https://mwidlake.files.wordpress.com/2021/01/screenhunter_-634.jpg?w=474... 474w, https://mwidlake.files.wordpress.com/2021/01/screenhunter_-634.jpg?w=150... 150w, https://mwidlake.files.wordpress.com/2021/01/screenhunter_-634.jpg?w=300... 300w" sizes="(max-width: 237px) 100vw, 237px" />

A Dutch oven is basically a heavy iron or aluminium casserole with a well fitting lid. You bake the bread with the lid on initially to trap moisture. I use an iron casserole dish about 26cm in diameter. The casserole needs to be about 5cm wider than your uncooked loaf, to allow for expansion. If you already have a casserole dish you might need to change your loaf size or the bowl/banneton you prove it in so that the loaf fits!

Pre-heat the oven and the casserole dish to 230C. Yes, 230C. It take about 15 minutes for my casserole to heat up fully.

Take out the casserole and  heavily dust the bottom with flour. You will know it is warm enough as the flour will smoke gently.

As carefully as you can, turn out the loaf into the casserole dish. I turn the banneton upside down and hold the loaf in place with my fingers, shake it slightly until the loaf drops onto my fingers and then I open my fingers to let it drop the 6 inches into the casserole. Do not let your skin touch the casserole dish, it hurts like hell! Slash the top of the loaf several times, again keeping the fingers away from the hot metal.

This is the main disadvantage of using a casserole, getting the loaf in and slashing the top is harder and the danger of a nasty burn is ever-present. I have tried turning the loaf out, slashing it and then transferring it to the casserole, but it knocked a fair bit of air out the loaf and reduced the rise.

Cook at 230C for 20 minutes. Remove the lid (the loaf will still be a cream colour) and cook for a further 15-20 mins. Turn the oven down to 160 and cook for a further 15-20 mins. You turn the temperature down more with the casserole as it retains heat for a while.

You might notice my oven says 235 and 165C. My oven temperature is a little cool (I tested with an oven thermometer) so I added 5C. You do get to know your oven when you do baking!

 

 

 

 

 

You loaf should now be dark golden brown. Remove the casserole from the oven. I put a little fan blowing air over the casserole for 5 minutes before I extract the loaf. Using a cloth to protect your fingers, take out the loaf and leave to cool on an a wire rack.

I swapped to the Dutch Oven method as a couple of friends recommended it and the flush of steam from the “oven with a tray” method was making the control panel of my oven go funny. I’ve already had it repaired once.

Having swapped, I think overall the Dutch Oven method gives a better loaf. I have far fewer issues with the loaf rise being uneven and part of the load bursting out the side or the crust “tearing” at the sides.

If I decide to make larger loaves I’ll simply swap to the oven-and-a-tray-of-water method.

 

 

 

Cooling

Once the loaf is out the oven I tend to start losing control of my salivary glands and I am desperate to eat it, so I use a little fan to help it cool in about 1/2 an hour. If you have more will power than I then it takes an hour or so for the loaf to cool naturally.

I love to cut open the loaf and eat it when it is still a little warm. The one disadvantage of this is that the loaf will lose extra moisture as a result of this, so any bread you save until tomorrow will be a little drier. I hardly ever manage to hold off cutting it early for the sake of a better experience tomorrow!

https://mwidlake.files.wordpress.com/2021/01/screenhunter_-637.jpg?w=498... 498w, https://mwidlake.files.wordpress.com/2021/01/screenhunter_-637.jpg?w=150... 150w, https://mwidlake.files.wordpress.com/2021/01/screenhunter_-637.jpg?w=300... 300w" sizes="(max-width: 249px) 100vw, 249px" />https://mwidlake.files.wordpress.com/2021/01/screenhunter_-638.jpg?w=480... 480w, https://mwidlake.files.wordpress.com/2021/01/screenhunter_-638.jpg?w=150... 150w, https://mwidlake.files.wordpress.com/2021/01/screenhunter_-638.jpg?w=300... 300w" sizes="(max-width: 240px) 100vw, 240px" />https://mwidlake.files.wordpress.com/2021/01/screenhunter_-639.jpg?w=420... 420w, https://mwidlake.files.wordpress.com/2021/01/screenhunter_-639.jpg?w=150... 150w, https://mwidlake.files.wordpress.com/2021/01/screenhunter_-639.jpg?w=300... 300w" sizes="(max-width: 210px) 100vw, 210px" />

Alterations to the recipe

I sometimes replace 150-200 grams of the white bread flour with spelt or mixed seed flour. It does seem to drop the rise a little though. I have tried adding a little dried bakers yeast to balance this but with limited success.

I have replaced all 500 grams of white bread flour with brown bread flour. It was OK, but despite me generally preferring brown bread,  with sourdough it just does not seem right to me.

I really like adding a teaspoon of smoked, sweet paprika to the mix. This is partly why I put the salt etc in the jug I later user for the water, I put the extra flavour in the jug too and the water washes out any flavouring that has remained in the jug.

Chop up a handful of sundried tomatoes (drained of their oil on kitchen paper as the oil seems to inhibit the rise) and add those with a good squirt (say 25ml) of double strength tomato puree.

 

1) You could use sea salt or Pink Himalayan salt instead of dirt-cheap table salt –  but it’s all the same stuff really, it’s dried out sea and mostly consists of the specific salt compound sodium chloride. The stuff dug out the ground is from a few hundred million years ago and sea salt is usually from drying out current sea water. The problem with salt that is not table salt is it is probably not as fine so it might impede rise more.

 

APEX Application Development Service on Oracle Cloud

https://oracle-base.com/blog/wp-content/uploads/2021/01/APX-211x300.png 211w" sizes="(max-width: 162px) 85vw, 162px" />

A few days ago Oracle announced the APEX Application Development Service on Oracle Cloud.

I had a sneak peak of this service last October. I’ve used APEX since it was know as Project Marvel, but despite this I’m renowned as the worst APEX developer on the planet. I think I was invited as a control subject… </p />
</p></div>

    	  	<div class=

Announcement: Both Of My Oracle Webinars Scheduled For February 2021 !!

I’m please to announce that both of my highly acclaimed webinars scheduled for February are now confirmed. These webinars are a must for any Oracle DBA or Developer interested in designing, developing or maintaining high performance, highly scalable Oracle-based applications or databases. However only a few places are currently available on each webinar with numbers […]

Supplemental Defect

Here’s an anomaly that appeared in a question on the ODC recently about tweaking the output of dbms_metadata.get_ddl(), As the title suggests, though, this posting isn’t about the dbms_metadata package it’s about supplemental logging and something that should not to be possible and may have unfortunate side effects.

We start with a little script that creates a table, inserts some data, adds a couple of constraints, and then introduces some supplemental logging requirements. As the script header indicates I’ve only tested this on 19.3.0.0:

rem
rem     Script:         supplemental_defect.sql
rem     Author:         Jonathan Lewis
rem     Dated:          Jan 2021
rem
rem     Last tested 
rem             19.3.0.0
rem

create table t1 (
        n1      number,
        n2      number,
        n3      number,
        n4      number
);

insert into t1 (n1, n2, n3, n4) values (1,2,3,4);
commit;

alter table t1 add constraint t1_pk primary key(n1, n2)
/

alter table t1 add constraint t1_uk unique(n3) using index (
        create index t1_uk on t1(n3, n4)
)
/

alter table t1 add supplemental log data (primary key, unique) columns
/

alter table t1 add supplemental log group t1_g1 (n1, n2) always
/

alter table t1 add supplemental log group t1_g2 (n1, n2) always
/

There’s nothing particularly special or complex about this code, and every statement runs successfully. You might notice that I’ve created two identical supplemental log groups, of course, and that’s clearly a mistake and I’d say that Oracle should have raised an error when I tried to create group t1_g2.

In fact my script is slightly worse than you might think at first sight because (n1, n2) is the primary key of the table, and I’ve added supplemental logging on the primary key already, so even supplemental log group t1_g1 really ought to raise an error as well.

Side note: you’ll notice that I’ve got a single-column unique constraint protected by non-unique multi-column index, and part of my supplemental log data specifies unique columns. If you check the syntax this option can be expressed as “unique” or “unique indexes” – which leaves me wondering whether Oracle my code would treat this as a command relating to n3 and n4, or just to n3.

So far all I’ve done is create a table in a way that’s left me with a couple of questions – let’s see what we get when we use dbms_metadata.get_ddl() to generate code to recreate the table:

begin
        dbms_metadata.set_transform_param(dbms_metadata.session_transform,'PRETTY',true);
        dbms_metadata.set_transform_param(dbms_metadata.session_transform,'SQLTERMINATOR',true);
        dbms_metadata.set_transform_param(dbms_metadata.session_transform,'SEGMENT_ATTRIBUTES',false);
        dbms_metadata.set_transform_param(dbms_metadata.session_transform,'STORAGE', false);
        dbms_metadata.set_transform_param(dbms_metadata.session_transform,'TABLESPACE',false);
        dbms_metadata.set_transform_param(dbms_metadata.session_transform,'CONSTRAINTS',true);
end;
/

set long 20000
set heading off

select dbms_metadata.get_ddl('TABLE','T1') from dual;

================================================================

  CREATE TABLE "TEST_USER"."T1"
   (	"N1" NUMBER,
	"N2" NUMBER,
	"N3" NUMBER,
	"N4" NUMBER,
	 CONSTRAINT "T1_PK" PRIMARY KEY ("N1", "N2")
  USING INDEX  ENABLE,
	 CONSTRAINT "T1_UK" UNIQUE ("N3")
  USING INDEX  ENABLE,
	 SUPPLEMENTAL LOG DATA (PRIMARY KEY) COLUMNS,
	 SUPPLEMENTAL LOG DATA (UNIQUE INDEX) COLUMNS,
	 SUPPLEMENTAL LOG GROUP "T1_G1" ("N1", "N2") ALWAYS,
	 SUPPLEMENTAL LOG GROUP "T1_G2" ("N1", "N2") ALWAYS
   ) ;

The “create table” statement includes 4 supplemental log clauses – notice how my original “two-part” clause for the primary key and unique constraints has been split into two – with the key word index being added to the latter.

You might note that if you try to execute this statement to recreate the table you’re going to run into an immediate problem – Oracle will create a unique singe-column index to protect the unique constraint – it doesn’t know that the constraint should be protected by a two-column non-unique index.

But there’s another problem that will protect you from the indexing issue. Here’s what I get when I try to execute the statement:

	 SUPPLEMENTAL LOG GROUP "T1_G1" ("N1", "N2") ALWAYS,
	                                *
ERROR at line 12:
ORA-02261: such unique or primary key already exists in the table

The funniest thing about this error comes when you look at the error message file ($ORACLE_HOME/rdbms/mesg/oraus.msg in my case), where we can find the “Cause” and “Action” that Oracle supplies for the error:

02261, 00000, "such unique or primary key already exists in the table"
// *Cause: Self-evident.
// *Action: Remove the extra key.

It’s not too difficult to guess why the error has appeared but “Self-evident” seems a little optimistic. In fact your first guess about the error may be wrong. Obviously I could simply delete the lines that create the t1_g1 and t1_g2 logs since they appear to be redundant copies of the (primary key) supplemental log – but what if I just delete the line that creates the (primary key) supplemental log? Oracle still raises the ORA-02261 error. It’s only when I delete the declaration of the primary key (and I can leave the declaration of the (primary key) supplemental log in place) that the error stops appearing and I recreate the table … even if I’ve left the decarations of the two supplemental log groups on (n1, n2) in place.

On the plus side – although the export/import utilities expdp and impdp make use of the dbms_metadata package they don’t run into the same problem. After I’d exported, dropped and imported the t1 table with the sqlfile=xxx.sql option this is how impdp reported the code it had run to recreate the table (edited to remove the space management bits):

CREATE TABLE "TEST_USER"."T1" 
   (    "N1" NUMBER, 
        "N2" NUMBER, 
        "N3" NUMBER, 
        "N4" NUMBER
   ) 
;

ALTER TABLE "TEST_USER"."T1" ADD CONSTRAINT "T1_PK" PRIMARY KEY ("N1", "N2")
;
ALTER TABLE "TEST_USER"."T1" ADD CONSTRAINT "T1_UK" UNIQUE ("N3")
;
ALTER TABLE "TEST_USER"."T1" ADD SUPPLEMENTAL LOG DATA (UNIQUE INDEX) COLUMNS;
ALTER TABLE "TEST_USER"."T1" ADD SUPPLEMENTAL LOG DATA (PRIMARY KEY) COLUMNS;
ALTER TABLE "TEST_USER"."T1" ADD SUPPLEMENTAL LOG GROUP "T1_G1" ("N1", "N2") ALWAYS;
ALTER TABLE "TEST_USER"."T1" ADD SUPPLEMENTAL LOG GROUP "T1_G2" ("N1", "N2") ALWAYS;

The underlying code for expdp/impdp generates constraints separately from the table and then adds the constraints to the tables – and it views supplemental logs as a variant on constraints (you can stop the supplemental log clauses appearing by using the dbms_metadata.transform_param() procedure to disable the generation of constraints) and adds them later.

You might notice in passing that the index created by impdp to support the unique constraint is NOT the index originally specified. This is a very old problem – though the nature of the problem has varied with time – so watch out if you move data from a production system to a test system using export/import.

Summary

The internal code to handle supplemental logging allows you to create redundant supplemental log groups that will cause errors if you use dbms_metadata.get_ddl() in the most straightforward way to generate code to recreate the table.

The error message that appears if you haven’t allowed for the anomaly is a little counter-intuitive.