settembre 2007 - Posts

Workshop: Road to Katmai
27 settembre 07 10.27 | abenedetti | with no comments

Il prossimo mercoledì 3 ottobre, a Segrate, avremo il prossimo Workshop di UGISS: "Road to Katmai".
http://www.ugiss.org/road+to+katmai.aspx

Nella prima sessione del pomeriggio, per la track Database Developer, presenterò quelle che sono le novità introdotte in SQL Server 2008.

Questa l'agenda:

  • T-SQL enhancements
    • Declare
    • Nuovi operatori
    • Row value constructor
  • Merge
  • New Date / Time data types 
    • DATE
    • TIME
    • DATETIME2
    • DATETIMEOFFSET
  • HierarchyID

Ci sono ancora posti e, come sempre, l'iscrizione è gratuita!

Filed under:
NHibernate & SQL Server: occhio alle performance
24 settembre 07 11.48 | abenedetti | 2 comment(s)

Prendo spunto da un thread del newsgroup italiano di SQL Server (news://microsoft.public.it.sql) a cui ho risposto oggi per sottolineare come, spesso, sia il caso di guardare quello che ci viene fatto in automatico da tool più o meno potenti.

Venivano presentate due query, apparentemente simili, con identica clausola where, che producevano due risultati (in termini di performance) molto distanti tra loro.

Queste le interrogazioni:

select * FROM eventlog
WHERE EventId = '12'
AND sid= '00e9e52d-0ef3-455b-b264-c04156ac4631'
and EventDetail =  'Ricevuta Risposta da Server Web '

exec sp_executesql N'SELECT * FROM EventLog
WHERE EventId = @p0
and Sid = @p1
and EventDetail = @p2',
N'@p0 nvarchar(2),
@p1 nvarchar(36),
@p2 nvarchar(42)',
@p0 = N'12',
@p1 = N'00e9e52d-0ef3-455b-b264-c04156ac4631',
@p2 = N'Ricevuta Risposta da Server Web '

I piani di esecuzione riportavano:
QUERY 1: Index Seek cost 75% (2 secondi di esecuzione)
QUERY 2: Index Scan cost 94% (32 secondi di esecuzione)

Questa la struttura della tabella interessata:

CREATE TABLE [dbo].[EventLog] (
 [DateTime] [int] NOT NULL ,
 [EventSource] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CS_AS NULL ,
 [Sid] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CS_AS NULL ,
 [EventId] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CS_AS NULL ,
 [EventDetail] [varchar] (7000) COLLATE SQL_Latin1_General_CP1_CS_AS NULL ,
 [EventData] [binary] (200) NULL
) ON [PRIMARY]
GO

 CREATE  CLUSTERED  INDEX [EventLog1] ON [dbo].[EventLog]([Sid], [DateTime])
ON [PRIMARY]
GO

Avete già visto il problema?
Notate la definizione della tabella ed invece la definizione dei parametri per la sp_executesql?

Ecco il motivo della differenza: il mapping dei parametri fatto in maniera automatica da NHibernate.

Basta modificare la seconda query con i metadati corretti, ovvero in:

exec sp_executesql N'SELECT * FROM EventLog
WHERE EventId = @p0
and Sid = @p1
and EventDetail = @p2',
N'@p0 varchar(50),
@p1 varchar(50),
@p2 varchar(7000)',
@p0 = N'12',
@p1 = N'00e9e52d-0ef3-455b-b264-c04156ac4631',
@p2 = N'Ricevuta Risposta da Server Web '

che andiamo ad aiutare l'optimizer per poter ricevere un ottimo CLUSTERED INDEX SEEK.

Attenzione che anche voi potreste dire: "purtroppo la query è stata generata da NHibernate, pertanto non me ne sono nemmeno accorto"

Un ultimo consiglio: se si fosse continuato a testare il software in casa, con alcune centinaia di record, il problema non si sarebbe mai incontrato. Sono bastate 10 milioni di righe per trovarlo...

Ancora un altro motivo per simulare la vita reale che dovranno avere i nostri db... ;-)

Filed under:
DAC e Management Studio
24 settembre 07 10.26 | abenedetti | 4 comment(s)

La funzionalità DAC, Dedicated Administrator Connection, è un'interessante novità di SQL Server 2005.
Consente di arrivare all'istanza anche in casi in cui questa non risponda più.

Si dice solo che questa funzionalità è utilizzabile da riga di comando (SQLCMD).
In realtà è utilizzabile anche dal Management Studio, semplicemente inserendo nella finestra di connessione "ADMIN:nomeIstanza" al posto del solito "nomeIstanza".

Attenzione inoltre che per utilizzare la DAC su istanze remote (non sulla propria macchina locale) questa funzionalità deve essere abilitata tramite la Surface Area Configuration.

Ovvero:

  1. apriamo Surface Area Configuration
  2. selezioniamo "surface area configuration for features"
  3. scegliamo l'istanza su cui vogliamo lavorare
  4. espandiamo il nodo database engine
  5. selezioniamo DAC
  6. il checkbox "enable remote DAC" deve essere selezionato

Se l'istanza è locale la connessione tramite "ADMIN:nomeIstanza" è già possibile utilizzarla senza problemi.

Filed under:
Windows Vista, Virtual PC, RAM, Net Stop, performance, ...
23 settembre 07 11.57 | abenedetti | 1 comment(s)

Quando lavoro / studio / gioco con istanze virtuali (come adesso per SQL Server 2008) cerco di risparmiare qualche MB prezioso di RAM sulla macchina host per avere le migliori performance possibili sulla macchina virtuale.

Mi sono deciso, quindi, di stilare un elenco di quei servizi che posso spegnere per recuperare risorse preziose.

I principali:

1. desktop window manager (aero)
2. windows search
3. sql server (se non devo far comunicare l'istanza virtuale con l'host)
4. iis
5. www

Non stoppo i reporting sevices perchè quel servizio non lo tengo in start automatico, ma lo avvio al momento del bisogno, così come il servizio OLAP.

Oltre a questi "principali" ho collezionato anche vari altri servizi (defender, time, help, second login, ...) e, naturalmente, anche l'applicazione sidebar.exe.

A questo punto il gioco è stato quello di metterli tutti in un file BAT che eseguo al momento del bisogno.

Ecco il mio file stopServices.bat:

net stop uxsms
net stop wsearch
net stop wscsvc
net stop mssqlserver
net stop iisadmin
net stop w3svc
net stop EMDMgmt
net stop TabletInputService
net stop WMPNetworkSvc
net stop WerSvc
net stop wuauserv
net stop iphlpsvc
net stop MpsSvc
net stop W32Time
net stop WinDefend
net stop upnphost
net stop SSDPSRV
net stop seclogon
net stop RemoteRegistry
taskkill /f /IM sidebar.exe

I risultati sulla mia macchina (Windows Vista Ultimate, 2 Gb Ram):

Appena startata: 932 MB di ram utilizzata (46%)
Dopo lo stop dei servizi: 656 MB

Ovvero quasi 300 MB portati a casa... ;-)

Consigli / critiche / suggerimenti?

Filed under:
WPC e Solid Quality Learning
21 settembre 07 08.05 | abenedetti | with no comments

Anche quest'anno si sta avvicinando la WPC (22 - 26 Ottobre).
Anchq quest'anno la squadra sarà presente al completo.

Ecco le sessioni che Solid Quality Learning Italia terrà sul palco:

A breve rilasceremo la nostra intera agenda di corsi e di formazione.
E vedrete che non mancheranno le sorprese!

Filed under:
Workshop 3 Ottobre
21 settembre 07 07.55 | abenedetti | with no comments

Ci sono ancora una decina di giorni per iscriversi al prossimo Workshop di UGISS.

Per adesso abbiamo un buon numero (a tre cifre) di iscrizioni, ma non siamo ancora sold-out.

L'agenda è di tutto rispetto:

  • [DBA & DBD] SQL Server 2008 Public CTP - Data Platform Overview
  • [DBA & DBD] Database & Time-Dependent Data - Parte 2
  • SQL Server 2008 Public CTP - Novità per gli amministratori
  • SQL Server 2008 Public CTP - Novità per gli sviluppatori
  • Monitoring & Testing Tools
  • Advanced SQLCLR: ricerca di testi simili
  • SQL Server 2005 Compact Edition: Advanced Concepts

Tutti i dettagli qui: http://www.ugiss.org/road+to+katmai.aspx

Service Broker Tutorial
21 settembre 07 12.34 | abenedetti | 1 comment(s)

Per chi ancora non ha messo le mani sul Service Broker, una delle funzionalità che io e Davide amiamo tanto, segnalo che su MSDN Online è stato pubblicato un nuovo tutorial.

Lo trovate qui

Il tutorial affronta tutti i casi possibili:

Filed under:
Il carello della spesa di questo mese
20 settembre 07 11.46 | abenedetti | 6 comment(s)

Come capita quasi ogni mese, stiamo finendo di definire, con il mio team, la lista della spesa presso Amazon.

Questi i libri che sono nel carrello:

Critiche / suggerimenti / proposte?

GO ed escuzioni multiple della stessa istruzione
19 settembre 07 02.43 | abenedetti | 6 comment(s)

Il comando GO che utilizziamo per separare in batch le istruzioni TSQL può essere associato ad un valore numerico.
A cosa server tale valore?
A far eseguire N volte le istruzioni contenute nel batch.

Ovvero scrivendo un'istruzione come:

PRINT 'ciao'
GO 3

Otteniamo questo risultato:

Beginning execution loop
ciao
ciao
ciao
Batch execution completed 3 times.

La cosa si fa interessante in tutti quegli scenari in cui abbiamo la necessità di popolare alcune tabelle con dati di test o di prova.
Qualcose del genere:

USE tempdb
GO

CREATE TABLE test
(
idRecord SMALLINT,
dateInsert DATETIME DEFAULT (GETDATE()),
col VARCHAR(35)
)
GO

INSERT test (col) VALUES ('aaa bbb ccc')
GO 1000

SELECT COUNT(1) FROM test
GO

DROP TABLE test
GO

Il risultato:

Batch execution completed 1000 times.

-----------
1000

Filed under:
Aggiornamento BOL e nuovo blog
19 settembre 07 02.23 | abenedetti | with no comments

E' stato pubblicato l'aggiornamento di Settembre dei booksonline di SQL Server 2005 qui.
Il team della documentazione ha aperto (finalmente) anche un suo blog: http://blogs.msdn.com/sqlserverue/

Filed under:
Microsoft acquires Dundas technology for SQL SERVER 2008
13 settembre 07 10.44 | abenedetti | 1 comment(s)

Oh, oh... Scusate, ma questa me l'ero persa (e la notizia è del 4 giugno 2007):

"Microsoft Corp. has acquired Dundas's data visualization technology, which will be part of the final feature set for Microsoft SQL Server 2008 Reporting Services.

The Dundas products to be incorporated into SQL Server 2008 include Dundas Chart for Reporting Services, Dundas Gauge for Reporting Services and Dundas Calendar for Reporting Services."

http://www.dundas.com/Company/Media/PressSQL2008.aspx

Filed under:
Rebuild di tutti gli indici
11 settembre 07 11.22 | abenedetti | 4 comment(s)

Oggi, durante pesanti lavori su un database, si è presentata la necessità di effettuare un'operazione di rebuild degli indici su tutte le tabelle.

Il metodo che ho suggerito io, anche se utilizza una SP non documentata (ma che anche su SQL 2005 gira tranquillamente), è solo di una riga:

USE mioDatabase
GO
EXEC sp_MSforeachtable @command1="print '?' DBCC DBREINDEX ('?', ' ', 80)"
GO

Filed under:
Template Personali: automatizzare la scrittura dei nostri oggetti
10 settembre 07 12.07 | abenedetti | 2 comment(s)

Con Emanuele sto partecipando ad un progetto mooolto interessante, fosse solo per la voglia di sperimentare: Visual Studio 2008, SQL Server 2008.

Il database delle applicazioni che stiamo progettando ha, al suo interno, alcune decine di tabelle di cui molte sono costruite in modo simile, almeno in parte.
Come se aveste diverste tabelle con una parte di struttura identica, supponete:

idRecord int primary key identity(1,1),
dateInsert datetime default(getdate()),
owner tinyint foreign key references dbo.users(idUser),
...

La prima domanda allora è stata: "Possiamo automatizzare la creazione di queste tabelle, ad esempio sfruttando i template?"

Certamente!

Il Management Studio offre template per automatizzare (o anche per ricordare :-)) istruzioni SQL...
I template risiedono in una cartella che potete trovare in Windows Vista qui:

C:\Users\xxxx\AppData\Roaming\Microsoft\Microsoft SQL Server\90\Tools\Shell\Templates\Sql

Ovvero in Windows XP:

C:\Documents and Settings\xxxxx\Application Data\Microsoft\Microsoft SQL Server\90\Tools\Shell\Templates\Sql

Ci è sufficiente inserire il nostro file SQL nella cartella che preferiamo, o aggiungere un nostro nuovo foldere.

 

Se invece avessimo a disposizione uno share di rete, per poter mettere a disposizione dell'intero team i file necessari, potremmo utilizzare il menù Tools --> External Tools ed aggiungerne uno come:

Title = Template Azienda
Command = explorer.exe
Arguments = \\ilMioServer\laMiaCartella

Riusabilità del codice
09 settembre 07 09.12 | abenedetti | with no comments

Lo fa anche la Disney...

http://blog.thepimp.net/index.php/post/2007/07/05/Disney-Code-reuse

:-D

Filed under:
WITH ENCRYPTION: decifrare codice TSQL
06 settembre 07 03.09 | abenedetti | with no comments

SQL Server permette, tramite la clausola WITH ENCRYPTION, di mascherare i sorgenti del nostro codice, ad esempio di Stored Procedure, Viste, Trigger, ...
Sicuramente la cosa va bene per utente non smaliziati, per proteggere "abbastanza" le istruzioni e la nostra logica.

Dico "abbastanza" perchè, com'è risaputo esiste il modo di riportare in chiaro le istruzioni, un pò come avviene con la decompilazione di assembly .Net che non siano stati offuscati con programmi creati ad-hoc per questo.

Oggi, sul newsgroup italiano di SQL Server (news://microsoft.public.it.sql ) ho aiutato un collega a riportare "in vita" delle SP di un vecchio db di cui si erano persi i sorgenti.
Grazie ad una procedura discussa a suo tempo (novembre 2006) sul newsgroup SQL Server Security (news://microsoft.public.sqlserver.security ).

ATTENZIONE: prima di provare (se si vuol provare) fare un bel backup del db... ok?

/* lavoro sul tempdb per fare le mie prove */
USE tempdb
GO

/* mi costruisco una SP cifrata */
CREATE PROCEDURE dbo.up_test
WITH ENCRYPTION
AS
SELECT 1
UNION
SELECT 2
GO

/* mi costruisco la SP per decifrare */
CREATE PROCEDURE dbo.sp_SpDeObfuscation (@procedure sysname = NULL, @safety int = 1)
AS

/*
Name:           sp_SpDeObfuscation
Purpose:        Decrypt SP's in SQL 2005
Author:         Theo Ekelmans (t...@ekelmans.com)
Based on:       A script that is discussed in news://microsoft.public.sqlserver.security
Version:        1.0 - 2006-11-16
Changes:        none (yet)

Input:          exec sp_SpDeObfuscation '<sp_name>', 0

Output:         Switch to text output!

Note:           You need to use the Dedicated Administrator Connection with
                        SQL Server Management Studio be in DAC to be able to run this SP
*/

SET NOCOUNT ON
IF @safety = 1
        BEGIN
                PRINT 'CAUTION: THIS PROCEDURE DELETES AND REBUILDS THE ORIGINAL STORED PROCEDURE.'
                PRINT ' '
                PRINT 'MAKE A BACKUP OF YOUR DATABASE BEFORE RUNNING THIS PROCEDURE.'
                PRINT ' '
                PRINT 'IDEALLY, THIS PROCEDURE SHOULD BE RUN ON A NON-PRODUCTION COPY OF THE PROCEDURE.'
                PRINT ' '
                PRINT 'To run the procedure, change the @safety parameter to 0'
                RETURN 0
        END

DECLARE @intProcSpace bigint
DECLARE @t bigint
DECLARE @maxColID smallint
DECLARE @intEncrypted tinyint
DECLARE @procNameLength int

DECLARE @real_01 nvarchar(max)
DECLARE @fake_01 nvarchar(max)
DECLARE @fake_encrypt_01 nvarchar(max)
DECLARE @real_decrypt_01 nvarchar(max)
DECLARE @real_decrypt_01a nvarchar(max)

-- create this table for later use
create table    #output (       [ident] [int] IDENTITY (1, 1) NOT NULL ,
                                                        [real_decrypt] NVARCHAR(MAX) )

SELECT  @maxColID = max(subobjid)
FROM    sys.sysobjvalues
WHERE   objid = object_id(@procedure)

select  @procNameLength = datalength(@procedure) + 29

select @real_decrypt_01a = ''

-- extract the encrypted imageval rows from sys.sysobjvalues
SET     @real_01=       (       SELECT  imageval
                                        FROM    sys.sysobjvalues
                                        WHERE   objid = object_id(@procedure)
                                        and             valclass = 1
                                        and             subobjid = 1 )

-- We'll begin the transaction and roll it back later
BEGIN TRAN

-- alter the original procedure, replacing with dashes ( the cast('-' as nvarchar(max)) is to allow for SP's larger than 4K)
SET     @fake_01 = 'ALTER PROCEDURE '+ @procedure +' WITH ENCRYPTION AS ' + REPLICATE(cast('-' as nvarchar(max)),  40003 - @procNameLength)

EXECUTE (@fake_01)

-- extract the encrypted fake imageval rows from sys.sysobjvalues
SET @fake_encrypt_01=(  SELECT  imageval
                                                FROM    sys.sysobjvalues
                                                WHERE   objid = object_id(@procedure)
                                                and             valclass = 1 and subobjid = 1)

SET @fake_01 = 'CREATE PROCEDURE '+ @procedure +' WITH ENCRYPTION AS ' + REPLICATE(cast('-' as nvarchar(max)), 40003 - @procNameLength)

--start counter
SET @intProcSpace=1

--fill temporary variable with with a filler character ( the cast(N'A' as nvarchar(max)) is to allow for SP's larger than 4K)
SET @real_decrypt_01 = replicate(cast(N'A' as nvarchar(max)), (datalength(@real_01)  /2    ))

--loop through each of the variables sets of variables, building the real variable one byte at a time.
SET @intProcSpace=1

-- Go through each @real_xx variable and decrypt it, as necessary
WHILE @intProcSpace <= (datalength(@real_01)/2)
        BEGIN
                --xor real & fake & fake encrypted
                SET @real_decrypt_01 = stuff(   @real_decrypt_01,
                     @intProcSpace,
                                                                                1,
                                                                                NCHAR   (       UNICODE(        substring(@real_01,                     @intProcSpace, 1)       )
                                                                                ^               (       UNICODE(        substring(@fake_01,                     @intProcSpace, 1)       )
                                                                                ^                       UNICODE(        substring(@fake_encrypt_01, @intProcSpace, 1)   )
                                                                                                )))

                SET @intProcSpace=@intProcSpace+1
        END

-- Load the variables into #output for handling by sp_helptext logic
insert #output (real_decrypt) select @real_decrypt_01

--select real_decrypt AS '#output check' from #output  -- Testing

-- -------------------------------------
-- Beginning of extract from sp_helptext
-- -------------------------------------
declare @dbname                 sysname
declare @BlankSpaceAdded   int
declare @BasePos                int
declare @CurrentPos             int
declare @TextLength             int
declare @LineId                 int
declare @AddOnLen               int
declare @LFCR                   int --lengths of line feed carriage return
declare @DefinedLength  int
declare @SyscomText             nvarchar(max)
declare @Line                   nvarchar(255)

Select @DefinedLength = 255
SELECT @BlankSpaceAdded = 0

--Keeps track of blank spaces at end of lines. Note Len function ignores trailing blank spaces
CREATE TABLE #CommentText (     LineId  int ,
                                                        Text    nvarchar(255) collate database_default)

-- use #output instead of sys.sysobjvalues
DECLARE ms_crs_syscom  CURSOR LOCAL FOR
        SELECT  real_decrypt
        from    #output
    ORDER BY
                        ident
        FOR READ ONLY

--  Else get the text.
SELECT @LFCR = 2
SELECT @LineId = 1

OPEN ms_crs_syscom

FETCH NEXT FROM ms_crs_syscom into @SyscomText

WHILE @@fetch_status >= 0
        BEGIN

                SELECT  @BasePos    = 1
                SELECT  @CurrentPos = 1
                SELECT  @TextLength = LEN(@SyscomText)

                WHILE @CurrentPos  != 0
                        BEGIN
                                --Looking for end of line followed by carriage return
                                SELECT @CurrentPos =  CHARINDEX(char(13)+char(10), @SyscomText, @BasePos)

                                --If carriage return found
                                IF @CurrentPos != 0
                                        BEGIN
                                                --If new value for @Lines length will be > then the set length then insert current contents of @line and proceed.
                                                While (isnull(LEN(@Line),0) + @BlankSpaceAdded + @CurrentPos-@BasePos + @LFCR) > @DefinedLength
                                                        BEGIN
                                                                SELECT @AddOnLen = @DefinedLength-(isnull(LEN(@Line),0) +@BlankSpaceAdded)

                                                                INSERT #CommentText VALUES ( @LineId, isnull(@Line, N'') + isnull(SUBSTRING(@SyscomText, @BasePos, @AddOnLen), N''))
                                                                SELECT @Line = NULL, @LineId = @LineId + 1, @BasePos = @BasePos + @AddOnLen, @BlankSpaceAdded = 0
                                                        END

                                                SELECT @Line    = isnull(@Line, N'') + isnull(SUBSTRING(@SyscomText, @BasePos, @CurrentPos-@BasePos + @LFCR), N'')
                                                SELECT @BasePos = @CurrentPos+2

                                                INSERT #CommentText VALUES( @LineId, @Line )
                                                SELECT @LineId = @LineId + 1

                                                SELECT @Line = NULL
                                        END
                                ELSE --else carriage return not found
                                        BEGIN
                                                IF @BasePos <= @TextLength
                                                        BEGIN
                                                                --If new value for @Lines length will be > then the defined length  
                                                                While (isnull(LEN(@Line),0) + @BlankSpaceAdded + @TextLength-@BasePos+1 ) > @DefinedLength
                                                                        BEGIN
                                                                                SELECT @AddOnLen = @DefinedLength - (isnull(LEN(@Line),0) + @BlankSpaceAdded)

                                                                                INSERT #CommentText VALUES ( @LineId, isnull(@Line, N'') + isnull(SUBSTRING(@SyscomText, @BasePos, @AddOnLen), N''))
                                                                                SELECT @Line = NULL, @LineId = @LineId + 1,  @BasePos = @BasePos + @AddOnLen, @BlankSpaceAdded = 0
                                                                        END

                                                                SELECT @Line = isnull(@Line, N'') + isnull(SUBSTRING(@SyscomText, @BasePos, @TextLength-@BasePos+1 ), N'')

                                                                if LEN(@Line) < @DefinedLength and charindex(' ', @SyscomText, @TextLength+1 ) > 0
                                                                        BEGIN
                                                                                SELECT @Line = @Line + ' ', @BlankSpaceAdded = 1
                                                                        END
                                                        END
                                        END
                                END

                FETCH NEXT FROM ms_crs_syscom into @SyscomText
        END

IF @Line is NOT NULL INSERT #CommentText VALUES( @LineId, @Line )

select Text from #CommentText order by LineId

-- Clean up
CLOSE  ms_crs_syscom
DEALLOCATE  ms_crs_syscom
DROP TABLE  #CommentText

-- -------------------------------------
-- End of extract from sp_helptext
-- -------------------------------------
ROLLBACK TRAN
DROP TABLE  #output

-- Drop the procedure that was setup with dashes and rebuild it with the good stuff

GO

SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO

A questo punto:
E' necessario connettersi con la DAC, Dedicated Administrator Connection (altrimenti non potrei avere acesso a tabelle di sistema come la sys.sysobjvalues).
Quindi: è necesasrio verificare tramite l'utility Surface Area Configuration che questa sia abilitata...

Per connettersi tramite DAC, da Management Studio, invece di connettersi a "nomeMacchina", bisogna connetersi a "ADMIN:nomeMacchina".

Quindi eseguire:

EXEC dbo.sp_SpDeObfuscation 'up_test', 0

Consiglio spassionato: fare un backup dei sorgenti del db, delle istruzioni di creazione degli oggetti, dovrebbe essere LA regola! ;-)

Filed under:

This Blog

Syndication