PyOhio registration, schedule, sponsors…

Catherine | Jul 2, 2009 13:09 -0600
Things are really coming together!

PyOhio

  • PyOhio registration is open. 38 people have signed up in just over a day!
  • The talk schedule is up. 25 talks, my goodness.
  • Oracle Technology Network and Intellovations have stepped forward as sponsors.
  • Oracle is also sending Todd Trichler to cooperate with me on a two-hour tutorial on Oracle/Python/Linux. I'm enjoying the preparations so far - I think you'll like it if you have any interest in databases.

SQLDeveloper Data Modeler is now production

Byte64 | Jul 2, 2009 05:18 -0600
Always check out the original article at http://www.oraclequirks.com for latest comments, fixes and updates.Just a "quickie" to let you know that as of today you can download SQLDeveloper Data Modeler tool release 2.0.0.57.0.After installing as suggested on the official page, in case you are running the tool on Mac OS X, you might get the following java exceptions:Exception in thread "main"

Oracle SQL Developer Data Modeler is Production

Sue | Jul 2, 2009 01:28 -0600
Well,well, well... it's July and in the UK the temperatures have reached the 30's. This might be normal for many of you, but for us, the gardens, the houses and the dogs are not quite used to it. Now for those of you thinking that this has become a biannual weather blog, you might not be far off. Since the last was in January and it was talk of snow...

So the reason for the lack of "blogginess" is multi-fold, some is that I have been working on our new product in addition to SQL Developer and some that I am writing a SQL Developer book. So it's not so much lack of material as lack of hours to write some more...

Now I do have so much to share with you, that I'm really keen to break this silence and get more words to paper (screen). Also there are many lovely little features in SQL Developer 1.5.4 that I find many of you don't know about, when I show them at events, that I should be doing short features more often. Mind you here's a blog on REAL-TIME SQL Monitoring, a feature that I wanted to write about for ages and Doug Burns has done it instead. (Thanks Doug) I've also been sent a list of blog topic suggestions from an attendee at one of the recent events I attended, so there really is no excuse.

Now that new tool: Oracle SQL Data Modeler. It's production! We published all the related material yesterday and hopefully the news won't be overwhelmed by all the Oracle Fusion Middleware 11g annoucements happening this week. Still it's all good and all good news.

You can download it from the main OTN site, as it's a featured download there, or you can go to our Data Modeler homepage for links to the download and the FAQ and other supporting technical docs and examples. This page will be updated fairly regularly as we add more bits of collateral. Remember too to use the SQL Developer forum if you have questions, and when I return from my blogging holiday, I'll mix SQL Developer and Data Modeler news and details here.

In case you upgraded to firefox 3.5 on Mac OS X and the bookmarks are gone…

Byte64 | Jul 2, 2009 00:19 -0600
Always check out the original article at http://www.oraclequirks.com for latest comments, fixes and updates.This morning i realized that Firefox 3.5 had been released.Soon after installing the new version, i had a feeling that something went wrong with my current bookmarks library because many recent entries were gone.Actually i think that firefox restored (for some unknown reason) an old version

Zend Core Server

The Zend Core Server replaces the deprecated Zend Core for Oracle. I’ve put a quick installation guide here. It’s much nicer, and the licensed server is now the recommended direction from Oracle.

The community edition also installs MySQL, phpMySQLAdmin, and a brand new console. You should try it out.

Learning From Failure

oraclenerd | Jul 1, 2009 21:18 -0600
I think I began reading The Daily WTF about 4 years ago. I don't miss or skip a post.

I remember this one time, probably about the time I began reading the site, I had to automate a process to move files from one server to another. Originally, I had tried to create a network drive (yes, it was Windows) on the database server so that I could just use a simple Java class to read the directory and then load the files via DBMS_LOB.

I had ultimately decided on a service, but I didn't know how to write one for Windows. Then I found the Java Service Wrapper which would allow me to write the guts in Java and then install it on Windows as a service. Perfect.

Now that I had that settled, I had to figure out how to detect when a file was read to be moved. I decided on a looping mechanism, to check every minute or so, to see if a file was available. It looked something like this (I'm a tad rusty, so bear with me):
package project1;

import java.util.Date;

public class Class1
{
public static void main(String[] args)
{
Class1 class1 = new Class1();
Date d = new Date();
long l = d.getTime() + 1000000000;
String s = String.valueOf( l );

for ( int i = 0; i < l; i++ )
{
//some sort of MOD "wait" here, then check for the file
}
}
}
It wasn't pretty, but it seemed to work.

Then I got a call from the server admin.

SA: "You've got something running on this machine that's spiking the CPU."

Me: "Really? I can't think of anything."

SA: "Well, take a look and see if you can find anything."

Me: "10-4"

Sure enough, go into Task Manager and there's java.exe hogging up all the CPU. WTF?

I just ran this on my machine and you can see the CPU start to spike:


Off to Google to see what I can find. During my research, I found mention of a small method called Thread.sleep(long). So I replaced my brilliant add 1000000 to the current date with Thread.sleep(6000), or whatever equals 60 seconds. Problem solved.

A short time later I read a post on The Daily WTF about the same exact problem (I can't find the exact post for the life of me). The "victim" did the exact same thing I did. The solution was the Thread.sleep() call.

Me = FAIL

One more short example.

Over beers, a friend (see last entry) of mine and I were discussing the failure of the North Korean missile launch. I said, "Idiots." He said, "They're going to learn a lot more from that failure than they would have had it suceeded."

Spoken like a true engineer I guess.

The point? You learn by trying. You learn my doing. You learn by failing. Whether you realize it or not, you learn. (Well, some people don't, but that's another post). If you're reading here though, that probably means you have a passion for what you do. That means that you are trying. You are learning (maybe not here specifically ;).

Here's to trying and failing and hopefully trying and succeeding.

Antlr3 Code Too Big

Antlr3 can generate very large java files for complex grammars.
Javac can have a problem compiling them due to the size limit of static initializers in a class file.

An error like the following

: code too large
> > [12:56:12] public static final String[] tokenNames = new String[] {
> > [12:56:12] ^
> > [12:56:12] 1 error


My solution was to post process the generated Java files.
Moving a large section of static final initializers to a seperate interface(s).
Then reference (implement) the interface in the original Java file.


package oracle.dtools.ant.antlr3;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

import org.apache.tools.ant.Task;

public class Antlr3PostProcess extends Task {
private File oldTarget = null;
private String pkg = null;

private File newTarget= null;
private File currentInterface = null;
private int interfaceIndex = 0;
private int lineIndex = 0;
private final int MAXLINEINDEX = 1000;
private String name = null;

public Antlr3PostProcess(){
}

public String getPkg() {
return pkg;
}

public void setPkg(String pkg) {
this.pkg = pkg;
}

public File getTarget() {
return oldTarget;
}

public void setOldTarget(File oldTarget) {
name = oldTarget.getName().substring(0,oldTarget.getName().indexOf('.'));
this.oldTarget = oldTarget;
newTarget = new File(oldTarget.getParentFile(),name+"_TMPANTLR3POST.java");
}

public void execute() {
try{
boolean createFile = newTarget.createNewFile();
moveBitSetConstantsToInterface();
//delete the old target and rename the new target
oldTarget.delete();
newTarget.renameTo(oldTarget);
} catch (Exception e){

}
}

private void moveBitSetConstantsToInterface() throws IOException {
//open the target file
BufferedReader reader = new BufferedReader(new FileReader(oldTarget));
BufferedWriter writerInterface = getNewInterfaceWriter();
BufferedWriter writerNewTarget = new BufferedWriter(new FileWriter(newTarget));

//... Loop as long as there are input lines.
String line = null;
while ((line=reader.readLine()) != null) {
if(lineIndex > MAXLINEINDEX) {
lineIndex =0;
closeInterface(writerInterface) ;
writerInterface = getNewInterfaceWriter();
}
if(line.startsWith(" public static final BitSet FOLLOW_")){
lineIndex++;
writerInterface.write(line);
writerInterface.newLine(); // Write system dependent end of line.
} else if (line.startsWith("public class "+name+" extends Parser")) {
writerNewTarget.write("public class "+name+" extends Parser implements "+getInterfaceName() +" {");
writerNewTarget.newLine(); // Write system dependent end of line.
}
else {
writerNewTarget.write(line);
writerNewTarget.newLine(); // Write system dependent end of line.
}

}
closeInterface(writerInterface);
//... Close reader and writer.
reader.close(); // Close to unlock.
//writerInterface.close(); // Close to unlock and flush to disk.
writerNewTarget.close(); // Close to unlock and flush to disk.
createInterfaceGroup();
}


private void createInterfaceGroup() throws IOException {
currentInterface = new File(oldTarget.getParentFile(),getInterfaceName()+".java");
boolean createFile =currentInterface.createNewFile();
BufferedWriter writerInterface = new BufferedWriter(new FileWriter(currentInterface));
writerInterface.write("package "+pkg+";");
writerInterface.newLine();
writerInterface.write("import org.antlr.runtime.BitSet;");
writerInterface.newLine();
writerInterface.write("public interface "+getInterfaceName()+" extends ");
writerInterface.write(getInterfaceName()+"1");
for(int i=2;i<=interfaceIndex;i++){
writerInterface.write(","+getInterfaceName()+i);
}
writerInterface.write(" {");
writerInterface.newLine();
writerInterface.write("}");
writerInterface.close();

}

private void closeInterface(BufferedWriter writerInterface) throws IOException {
writerInterface.newLine();
writerInterface.write("}");
writerInterface.close();
}

private BufferedWriter getNewInterfaceWriter() throws IOException {
BufferedWriter writerInterface = new BufferedWriter(new FileWriter(getNextInterfaceFile()));
createInterfaceHeading(writerInterface,currentInterface.getName().substring(0,currentInterface.getName().indexOf('.')),pkg);
return writerInterface;

}

private void createInterfaceHeading(BufferedWriter writerInterface,String interfaceName, String interfacePkg) throws IOException {
writerInterface.write("package "+interfacePkg+";");
writerInterface.newLine();
writerInterface.write("import org.antlr.runtime.BitSet;");
writerInterface.newLine();
writerInterface.write("public interface "+interfaceName+" {");
writerInterface.newLine();
}

private File getNextInterfaceFile() throws IOException {
interfaceIndex++;
currentInterface = new File(oldTarget.getParentFile(),getInterfaceName()+interfaceIndex+".java");
boolean createFile =currentInterface.createNewFile();
return currentInterface;
}

private String getInterfaceName() {
return name+"BitSet";
}
}


Define the Ant Task
<taskdef name="Antlr3PostProcess" classname="oracle.dtools.ant.antlr3.Antlr3PostProcess" classpath="Your Classpath Here">
<classpath><path refid="antlr.3" /><path refid="string.template" /></classpath>
</taskdef>


Reference the task after the Antlr has generated the Java files, but before javac.
<replace file="${antlr3.output}/Db2Parser.java" token="public static final String[] tokenNames" value="public final String[] tokenNames"/>
<replace file="${antlr3.output}/Db2Parser.java" token="Db2Parser.tokenNames" value="tokenNames"/>
<Antlr3PostProcess oldTarget="${antlr3.output}/Db2Parser.java" pkg="oracle.dbtools.migration.parser.grammar.antlr3">
</Antlr3PostProcess>

Forms : A Translator Java Bean

Here is a Java Bean from Jesus Vallejo that works as a translator, based on the Google Translater project which can be found at "http://google-api-translate-java.googlecode.com/files/google-api-translate-java-0.53.jar". Read the article

Hybrid iPhone Development

maclochlainn | Jun 30, 2009 09:42 -0600

A colleague of mine just dropped by his new book on Developing Hybrid Applications for the iPhone. He covers Dashcode, Xcode, JavaScript, and Objective-C. He also covers how to use WebView and native SQLite database access from the iPhone.

It looks interesting. By the way, his blog is here.

Constraints: ENABLE NOVALIDATE

oraclenerd | Jun 29, 2009 19:58 -0600
Yesterday while perusing the Concepts Guide, I stumbled across the ENABLE NOVALIDATE keywords for the definition of a Foreign Key constraint. I've always known it was there, just never used it, or thought to use it.

It can be a big benefit while working on a legacy system.

Suppose you have a table, T_CHILD:
CREATE TABLE t_child
(
child_id NUMBER(10)
CONSTRAINT pk_childid PRIMARY KEY,
soon_to_be_parent_id NUMBER(10)
);

INSERT INTO t_child
( child_id,
soon_to_be_parent_id )
SELECT
rownum,
TRUNC( dbms_random.value( -9999, -1 ) )
FROM dual
CONNECT BY LEVEL <= 10;
This table has been around for quite some time. You decide that you would like to constrain the values in the SOON_TO_BE_PARENT_ID column. First, here's the data that exists:
CJUSTICE@TESTING>SELECT * FROM t_child;

CHILD_ID SOON_TO_BE_PARENT_ID
---------- --------------------
1 -5560
2 -1822
3 -2499
4 -7039
5 -8718
6 -1019
7 -9997
8 -9553
9 -4477
10 -1458
Now I'll create a table that will contain the values I want to constraint SOON_TO_BE_PARENT_ID to, call it a lookup or reference table.
CREATE TABLE t_parent
(
parent_id NUMBER(10)
CONSTRAINT pk_parentid PRIMARY KEY
);
I'll populate it with some data:
INSERT INTO t_parent( parent_id )
SELECT rownum
FROM dual
CONNECT BY LEVEL <= 10;

CJUSTICE@TESTING>SELECT * FROM T_PARENT;

PARENT_ID
----------
1
2
3
4
5
6
7
8
9
10

10 rows selected.
Now I'll add the constraint that references the PARENT_ID column of T_PARENT
ALTER TABLE t_child
ADD CONSTRAINT fk_parentid
FOREIGN KEY ( soon_to_be_parent_id )
REFERENCES t_parent( parent_id )
ENABLE
NOVALIDATE
;
and rename the column to PARENT_ID:
ALTER TABLE t_child RENAME COLUMN soon_to_be_parent_id TO parent_id;
What will this do? I should no longer be able to enter a value into T_CHILD.PARENT_ID that does not exist in T_PARENT, but it will ignore anything that already exists.
INSERT INTO t_child
( child_id,
parent_id )
VALUES
( 11,
11 );

INSERT INTO t_child
*
ERROR at line 1:
ORA-02291: integrity constraint (CJUSTICE.FK_PARENTID) violated - parent key not found
Perfect! Now I'll add a value that does exist in T_PARENT.
INSERT INTO t_child
( child_id,
parent_id )
VALUES
( 11,
10 );

1 row created.
Win!

This is just another reminder why you must read the Concepts Guide. By the way, I found the quote I was looking for from Mr. Kyte (h/t @boneist)
"...if you simply read the Concepts Guide...and retain just 10%..., you’ll already know 90% more than most people do"

PHP, LOBs, and Oracle

maclochlainn | Jun 29, 2009 18:35 -0600

I finally got around to summarizing how to use PHP to store, retrieve, and display CLOBs and BLOBs from an Oracle database. I think too often we default to BFILEs. I put all the code in zip files with instructions and suggestions for locations. This is really the second entry that goes with configuring Zend Server Community Edition or the DEPRECATED Zend Core for Oracle.

If you’re new to PHP, check out the Underground PHP and Oracle book from Christopher Jones and Alison Holloway. It’s FREE!

The Oracle LOB Processing entry is in this blog page. I know it means another click, but I’ll probably add and modify it over time. If you’ve got time and interest, take a look and let me know what you think and what improvements you’d like to see. Thanks.

Classic: DBMS_APPLICATION_INFO

oraclenerd | Jun 29, 2009 13:49 -0600
I've decided to post a few "classics" from long before anyone paid attention...as opposed to now, when 30 people pay attention. I originally posted this on 12/02/2007 at 10:04 PM which you can find here.

Instrumentation has something that I have come to rely on fairly heavily. I believe I first read about it on asktom, but the one that really spurred me on was this post on instrumentation on his personal blog.

Initially, I couldn't really wrap my head around instrumentation. I don't know why it was so difficult; I had a similar problem with sessions when I first started my career. I look back now and it just seems so obvious.

Now that I am doing datawarehouse work, nothing is fast. Fast to me is now one hour to load 30 or 40 million records. No more split second queries for me.

We currently use no tools. It's straight PL/SQL. Instrumentation of the code is ideal. Actually, it's more instrumentation to aid monitoring. The tool most easily used is provided by Oracle in the DBMS_APPLICATION_INFO package.

There are three subprograms that I use most, SET_MODULE, SET_ACTION and most importantly SET_SESSION_LONGOPS. I hadn't started using it until this year, I mainly stuck to the first two. SET_SESSION_LONGOPS is now part of my procedure/function template I've created in JDeveloper.

What it allows you to do is set a row in the v$session_longops view (I know it's not actually putting the row in the view...it's the underlying table, but I digress). You can then monitor how your job is doing.

Here's an example:
dbms_application_info.set_session_longops
( rindex => g_index,
slno => g_slno,
op_name => 'GETTING MEMBER DATA',
sofar => 0,
totalwork => l_table.COUNT + 1,
target_desc => 'GETTING MEMBER DATA' );
g_index and g_slno are global variables in the package. l_table is a PL/SQL TABLE OF VARCHAR2.

Now you can monitor the progress of your job in v$session_longops!

Here's the query I use:
SELECT 
username,
sid,
serial#,
TO_CHAR( start_time, 'MM/DD/YYYY HH24:MI:SS' ) start_ti,
time_remaining rem,
elapsed_seconds ela,
ROUND( ( sofar / REPLACE( totalwork, 0, 1 ) ) * 100, 2 ) per,
sofar,
totalwork work,
message,
target_desc
FROM v$session_longops
WHERE start_time >= SYSDATE - 1
ORDER BY start_time DESC
Now you too can sit for hours and watch your job move incrementally forward!

But seriously, it does help tremendously to know where a job is at. You can further use the SET_MODULE and SET_ACTION calls to see a specific point in the processing (inside a loop).

Here's the code in context:
PROCEDURE get_member_data
IS
l_exists INTEGER;
TYPE table_of_lobs IS TABLE OF VARCHAR2(3);
l_table TABLE_OF_LOBS := TABLE_OF_LOBS( 'COM', 'ORG' );
l_count INTEGER := 0;
BEGIN
--check to see if there is enrollment data, if not, move on
SELECT COUNT(*)
INTO l_exists
FROM members
WHERE rownum < 2;

IF l_exists = 1 THEN--data exists, truncate and reload

g_index := dbms_application_info.set_session_longops_nohint;

EXECUTE IMMEDIATE 'TRUNCATE TABLE member_stg';

g_audit_key := p_audit.begin_load
( p_targettable => 'MEMBER_STG',
p_loadsource => 'MEMBER_SOURCE',
p_loadstatus => 'PRE',
p_loadprogram => 'GET_MEMBER_DATA',
p_commenttext => 'INSERT' );

dbms_application_info.set_session_longops
( rindex => g_index,
slno => g_slno,
op_name => 'GETTING MEMBERS',
sofar => 0,
totalwork => l_table.COUNT + 1,
target_desc => 'GETTING MEMBERS' );

FOR i IN 1..l_table.COUNT LOOP
l_count := l_count + 1;

INSERT INTO member_stg
SELECT *
FROM members;

g_total_rows_affected := g_total_rows_affected + sql%rowcount;

COMMIT;

dbms_application_info.set_session_longops
( rindex => g_index,
slno => g_slno,
op_name => 'GETTING MEMBERS',
sofar => l_count,
totalwork => l_table.COUNT + 1,
target_desc => 'GETTING MEMBERS' );

END LOOP;

p_audit.end_load
( p_auditkey => g_audit_key,
p_loadstatus => 'SUC',
p_rowsuccess => g_total_rows_affected );

gather_table_stats
( p_tablename => 'MEMBER_STG',
p_schemaname => 'MYHOME' );

dbms_application_info.set_session_longops
( rindex => g_index,
slno => g_slno,
op_name => 'GETTING MEMBERS',
sofar => l_count + 1,
totalwork => l_table.COUNT + 1,
target_desc => 'GETTING MEMBERS' );

END IF;

EXCEPTION
WHEN others THEN
p_audit.failed_load
( p_auditkey => g_audit_key,
p_comments => SQLCODE || ' ' || SQLERRM );
RAISE;

END get_member_data;

Oracle Fusion 11g Middleware Launch Webcast

As I mentioned a while back, Fusion Middleware 11g will be launching, officially, on July 1. You can join the festivities in DC by logging into the webcast, July 1 at 10am EST. I don't know if the breakout sessions will be included but it will be at least the keynote and I would bet a demo, possibly even Fusion Apps (the future of EBusiness). I saw a demo of Fusion Apps at ODTUG Kaleidoscope and

Disabling a Trigger

Gerd Volberg | Jun 28, 2009 22:38 -0600
If you have a trigger and you want to quickly disable him, then just use the "_"-technique :



In this case we have a Key-Create-Record-Trigger which should be disabled. Rename the "-" to "_" in the name of the trigger and from that point on the trigger is a user-named-trigger, which won't fire through an event.



Important: You can't rename the trigger to "KEY-CREREC2" or something else. The hyphen is in forms reserved for event-triggers.

To activate the trigger rename it back to "-" and that's all.

Have fun
Gerd

Oracle Concepts: Data Integrity Rules

oraclenerd | Jun 28, 2009 21:30 -0600
I'm reading through the Concepts manual again as mentioned on last week.

I'm going to make a small effort to post some of the key concepts here over the next couple of weeks. If you've read through the Concepts Guide before, this can serve as a brief refresher. If not, good, you're exposed to something new.

Data Integrity Rules
This section describes the rules that can be applied to table columns to enforce different types of data integrity.

Null rule: A null rule is a rule defined on a single column that allows or disallows inserts or updates of rows containing a null (the absence of a value) in that column.

Unique column values: A unique value rule defined on a column (or set of columns) allows the insert or update of a row only if it contains a unique value in that column (or set of columns).

Primary key values: A primary key value rule defined on a key (a column or set of columns) specifies that each row in the table can be uniquely identified by the values in the key.

Referential integrity rules: A referential integrity rule is a rule defined on a key (a column or set of columns) in one table that guarantees that the values in that key match the values in a key in a related table (the referenced value).

Referential integrity also includes the rules that dictate what types of data manipulation are allowed on referenced values and how these actions affect dependent values. The rules associated with referential integrity are:

* Restrict: Disallows the update or deletion of referenced data.
* Set to null: When referenced data is updated or deleted, all associated dependent data is set to NULL.
* Set to default: When referenced data is updated or deleted, all associated dependent data is set to a default value.
* Cascade: When referenced data is updated, all associated dependent data is correspondingly updated. When a referenced row is deleted, all associated dependent rows are deleted.
* No action: Disallows the update or deletion of referenced data. This differs from RESTRICT in that it is checked at the end of the statement, or at the end of the transaction if the constraint is deferred. (Oracle Database uses No Action as its default action.)

Complex integrity checking: A user-defined rule for a column (or set of columns) that allows or disallows inserts, updates, or deletes of a row based on the value it contains for the column (or set of columns).
Reading on past the brief section to the Constraint States I found this nugget:
ENABLE NOVALIDATE means that the constraint is checked, but it does not have to be true for all rows. This allows existing rows to violate the constraint, while ensuring that all new or modified rows are valid.
This is a great tool for legacy systems. You have data in the column(s) that you can't really do anything with, but you want to insure that all future data that goes in that particular column(s) matches the parent key.

Of course the ideal is to somehow clean the data up, but you don't always have that option. This is a good first step towards to overall cleanup of your legacy system.

APEX: URL Syntax

oraclenerd | Jun 28, 2009 19:05 -0600
This is more for my own edification than anything. I always forget which place the ClearCache inhabits.
f?p=App:Page:Session:Request:Debug:ClearCache:itemNames:PrinterFriendly
With numbers so I don't have to count every time:
f?p=App1:Page2:Sess3:Requ4:Debug5:Cache6:items7:printer8

f?p=1:2:3:4:5:6:7:8
Link to the documentation is here.

Empythoning Oracle

Jeffrey Kemp | Jun 28, 2009 18:07 -0600
Quite a few months ago I read Wrapping Your Brain Around Oracle + Python and was somewhat interested but too busy to look into it in depth.Recently I've had more free time than I'd like, and wanted to make the most of it by learning a new language. As they say, "Master a few [programming languages]". I thought about a few languages, remembering several articles that cast Python in a good light.

Python: it’s been done before

Jeffrey Kemp | Jun 28, 2009 18:06 -0600
I've learned that with Python, if something takes more than a few lines to write, there's a very good chance that there's another way to do it but with one or two lines of code. Chances are someone else has already come across a similar problem, and written a module to solve it - and it's just a matter of importing their module and reusing it.The other day I blogged about some functions that

Twitter Weekly Updates for 2009-06-28

Lewi | Jun 28, 2009 16:23 -0600

Powered by Twitter Tools.

“Jerry” is a spamtard

APC | Jun 28, 2009 04:47 -0600
In fact he may well leave a spam comment on this post, touting his list of bridalwear sites. As "Jerry" in all likelihood doesn't read English the irony will be lost on him. I'm talking as though "Jerry" is human but probably he is a bot: I seem to remembering reading that somebody had cracked captchas a while back. Certainly "Jerry" has been the only spamtard persistent enough to spam every single post on this site, even my very first one (which possibly makes him the first person to visit that page, ever). "Laptop Battery", "Peter W" and "Eda" are lightweights by comparison.

This sorry state of affairs is my fault. I have allowed comments without moderation because I would rather zap the occasional spam than moderate all the comments. But, until Joel Garry alerted me, I had failed to notice that spamtards were spamming old posts. As Joel said, some of the stuff was nasty, really explicit pr0n sites. So, moderation for older posts is now in effect - it's already caught a couple.

In the meantime I have a large housekeeping task. One post - on multi-core licensing, bizarrely - has over 150 spam comments, and as I have already said, "Jerry" has spammed every single post. It is unfortunate that Blogger does not provide the functionality to delete comments in bulk, despite being desired for several years. So the only option is to delete each spam comment individually, which as Bill Scott has observed, is a rather user hostile design.

The question behind all this is why Blogger doesn't provide the functionality. It's not like it would be hard to offer a list of all the comments with a check box and a Delete All button. The Google forums (they own Blogger) have lots of questions but no useful advice. It is especially puzzling when compared to the excellent way GoogleMail handles spam.

Now I am off to zap a few more comments. With a song in my heart and a smile on my face, naturally.